@classytic/crm 0.8.0 → 1.0.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/dist/index.d.ts CHANGED
@@ -1,13 +1,12 @@
1
- import * as _$mongoose from "mongoose";
1
+ import { ModelBlueprint, PaginationConfig, PluginType, Repository } from "@classytic/mongokit";
2
2
  import { Connection, Model, Schema, SchemaDefinitionProperty } from "mongoose";
3
- import { PaginationConfig, PluginType, Repository } from "@classytic/mongokit";
4
3
  import { DomainEvent, EventHandler, EventMeta, EventTransport } from "@classytic/primitives/events";
5
- import * as _$_classytic_primitives_state_machine0 from "@classytic/primitives/state-machine";
4
+ import { InProcessEventBus } from "@classytic/primitives/event-infra";
6
5
  import { z } from "zod";
7
6
  import { StatusChangeEntry, StatusHistory, appendStatus, lastTransitionTo, latestEntry, timeInStatus } from "@classytic/primitives/status-history";
8
7
  import { OperationContext } from "@classytic/primitives/context";
9
8
  import { OutboxStore } from "@classytic/primitives/outbox";
10
-
9
+ import { HttpError } from "@classytic/repo-core/errors";
11
10
  //#region src/events/dispatch.d.ts
12
11
  /**
13
12
  * Context key for the post-commit event queue. `Symbol.for` so the host
@@ -59,12 +58,39 @@ declare class CrmEventDispatcher implements EventTransport {
59
58
  */
60
59
  declare function flushPending(transport: Pick<EventTransport, 'publish'> | undefined, events: DomainEvent[], logger?: CrmLogger): Promise<void>;
61
60
  //#endregion
61
+ //#region src/engine/modules.d.ts
62
+ /** Coarse CRM feature bundles. Keep entity-level switches out of the API. */
63
+ interface CrmModulesConfig {
64
+ /** Operational sales CRM: leads, accounts, contacts, opportunities and follow-up. */
65
+ sales?: boolean | undefined;
66
+ /** Customer communication: conversations, messages, consent and email events. */
67
+ engagement?: boolean | undefined;
68
+ /** Campaign and sequence automation. Implies engagement and sales. */
69
+ automation?: boolean | undefined;
70
+ }
71
+ interface ResolvedCrmModules {
72
+ readonly sales: boolean;
73
+ readonly engagement: boolean;
74
+ readonly automation: boolean;
75
+ }
76
+ declare const ALL_CRM_MODULES: ResolvedCrmModules;
77
+ /**
78
+ * Resolve module dependencies once at boot.
79
+ *
80
+ * Omitting `modules` preserves the pre-module package behaviour (everything
81
+ * enabled). Supplying it is opt-in: unspecified modules are disabled.
82
+ */
83
+ declare function resolveCrmModules(modules: CrmModulesConfig | undefined): ResolvedCrmModules;
84
+ //#endregion
62
85
  //#region src/models/tenant-field.d.ts
63
86
  type TenantFieldType = 'objectId' | 'string';
64
87
  interface SchemaBuildConfig {
65
88
  /** Schema type for the tenant column. Engine plumbs this everywhere. */
66
89
  tenantFieldType: TenantFieldType;
90
+ /** Optional Mongoose model namespace for hosts with generic model names. */
91
+ modelPrefix?: string | undefined;
67
92
  }
93
+ declare function crmModelName(config: SchemaBuildConfig, base: string): string;
68
94
  /**
69
95
  * Tenant-column schema definition. Use this anywhere a model has
70
96
  * `organizationId` — never write the raw type in a schema file.
@@ -74,7 +100,7 @@ declare function tenantFieldDef(config: SchemaBuildConfig): SchemaDefinitionProp
74
100
  //#region src/models/pipeline.schema.d.ts
75
101
  /** Mongoose document shape for a single embedded stage. */
76
102
  interface IStageDocument {
77
- _id: _$mongoose.Types.ObjectId;
103
+ _id: import('mongoose').Types.ObjectId;
78
104
  name: string;
79
105
  /** Ascending order in the pipeline. Treated as identifier on `moveToStage`. */
80
106
  sequence: number;
@@ -85,8 +111,8 @@ interface IStageDocument {
85
111
  }
86
112
  /** Mongoose document shape for a Pipeline. */
87
113
  interface IPipelineDocument {
88
- _id: _$mongoose.Types.ObjectId;
89
- organizationId: _$mongoose.Types.ObjectId | string;
114
+ _id: import('mongoose').Types.ObjectId;
115
+ organizationId: import('mongoose').Types.ObjectId | string;
90
116
  name: string;
91
117
  /** Deprecates the pipeline — existing opps still move, no new ones created. */
92
118
  isArchived: boolean;
@@ -138,8 +164,8 @@ interface ILeadEngagement {
138
164
  lastEngagedAt?: Date;
139
165
  }
140
166
  interface ILeadDocument {
141
- _id: _$mongoose.Types.ObjectId;
142
- organizationId: _$mongoose.Types.ObjectId | string;
167
+ _id: import('mongoose').Types.ObjectId;
168
+ organizationId: import('mongoose').Types.ObjectId | string;
143
169
  fullName: string;
144
170
  firstName?: string;
145
171
  lastName?: string;
@@ -155,11 +181,13 @@ interface ILeadDocument {
155
181
  statusHistory: ILeadStatusEntry[];
156
182
  ownerId?: string;
157
183
  tags: string[];
158
- convertedContactId?: _$mongoose.Types.ObjectId | string;
159
- convertedAccountId?: _$mongoose.Types.ObjectId | string;
160
- convertedOpportunityId?: _$mongoose.Types.ObjectId | string;
184
+ convertedContactId?: import('mongoose').Types.ObjectId | string;
185
+ convertedAccountId?: import('mongoose').Types.ObjectId | string;
186
+ convertedOpportunityId?: import('mongoose').Types.ObjectId | string;
161
187
  convertedAt?: Date;
162
188
  disqualifyReason?: string;
189
+ /** GDPR tombstone — set by `anonymize()` when PII was scrubbed. */
190
+ anonymizedAt?: Date;
163
191
  metadata?: Record<string, unknown>;
164
192
  createdAt: Date;
165
193
  updatedAt: Date;
@@ -189,12 +217,12 @@ interface IOpportunityStatusEntry {
189
217
  note?: string;
190
218
  }
191
219
  interface IOpportunityDocument {
192
- _id: _$mongoose.Types.ObjectId;
193
- organizationId: _$mongoose.Types.ObjectId | string;
220
+ _id: import('mongoose').Types.ObjectId;
221
+ organizationId: import('mongoose').Types.ObjectId | string;
194
222
  name: string;
195
- accountId?: _$mongoose.Types.ObjectId | string;
196
- primaryContactId?: _$mongoose.Types.ObjectId | string;
197
- pipelineId: _$mongoose.Types.ObjectId | string;
223
+ accountId?: import('mongoose').Types.ObjectId | string;
224
+ primaryContactId?: import('mongoose').Types.ObjectId | string;
225
+ pipelineId: import('mongoose').Types.ObjectId | string;
198
226
  stageId: string;
199
227
  status: OpportunityStatus;
200
228
  statusHistory: IOpportunityStatusEntry[];
@@ -203,9 +231,9 @@ interface IOpportunityDocument {
203
231
  probability: number;
204
232
  expectedCloseAt?: Date;
205
233
  closedAt?: Date;
206
- lostReasonId?: _$mongoose.Types.ObjectId | string;
234
+ lostReasonId?: import('mongoose').Types.ObjectId | string;
207
235
  ownerId?: string;
208
- sourceLeadId?: _$mongoose.Types.ObjectId | string;
236
+ sourceLeadId?: import('mongoose').Types.ObjectId | string;
209
237
  tags: string[];
210
238
  metadata?: Record<string, unknown>;
211
239
  createdAt: Date;
@@ -234,15 +262,17 @@ interface IContactInfoEmbedded {
234
262
  instagram?: string;
235
263
  }
236
264
  interface IContactDocument {
237
- _id: _$mongoose.Types.ObjectId;
238
- organizationId: _$mongoose.Types.ObjectId | string;
265
+ _id: import('mongoose').Types.ObjectId;
266
+ organizationId: import('mongoose').Types.ObjectId | string;
239
267
  name: IContactName;
240
268
  contact: IContactInfoEmbedded;
241
269
  jobTitle?: string;
242
- accountId?: _$mongoose.Types.ObjectId | string;
270
+ accountId?: import('mongoose').Types.ObjectId | string;
243
271
  ownerId?: string;
244
272
  tags: string[];
245
273
  metadata?: Record<string, unknown>;
274
+ /** GDPR tombstone — set by `anonymize()` when PII was scrubbed. */
275
+ anonymizedAt?: Date;
246
276
  createdAt: Date;
247
277
  updatedAt: Date;
248
278
  }
@@ -251,8 +281,8 @@ declare function createContactModel(connection: Connection, config: SchemaBuildC
251
281
  //#endregion
252
282
  //#region src/models/account.schema.d.ts
253
283
  interface IAccountDocument {
254
- _id: _$mongoose.Types.ObjectId;
255
- organizationId: _$mongoose.Types.ObjectId | string;
284
+ _id: import('mongoose').Types.ObjectId;
285
+ organizationId: import('mongoose').Types.ObjectId | string;
256
286
  name: string;
257
287
  domain?: string;
258
288
  industry?: string;
@@ -291,8 +321,8 @@ interface ICampaignStatusEntry {
291
321
  note?: string;
292
322
  }
293
323
  interface ICampaignDocument {
294
- _id: _$mongoose.Types.ObjectId;
295
- organizationId: _$mongoose.Types.ObjectId | string;
324
+ _id: import('mongoose').Types.ObjectId;
325
+ organizationId: import('mongoose').Types.ObjectId | string;
296
326
  name: string;
297
327
  description?: string;
298
328
  status: CampaignStatus;
@@ -342,10 +372,10 @@ interface ISubscriptionStatusEntry {
342
372
  note?: string;
343
373
  }
344
374
  interface ISubscriptionDocument {
345
- _id: _$mongoose.Types.ObjectId;
346
- organizationId: _$mongoose.Types.ObjectId | string;
347
- contactId: _$mongoose.Types.ObjectId | string;
348
- campaignId: _$mongoose.Types.ObjectId | string;
375
+ _id: import('mongoose').Types.ObjectId;
376
+ organizationId: import('mongoose').Types.ObjectId | string;
377
+ contactId: import('mongoose').Types.ObjectId | string;
378
+ campaignId: import('mongoose').Types.ObjectId | string;
349
379
  status: SubscriptionStatus;
350
380
  statusHistory: ISubscriptionStatusEntry[];
351
381
  source?: string;
@@ -376,9 +406,17 @@ type SuppressionReason = (typeof SUPPRESSION_REASONS)[number];
376
406
  //#endregion
377
407
  //#region src/models/suppression.schema.d.ts
378
408
  interface ISuppressionDocument {
379
- _id: _$mongoose.Types.ObjectId;
380
- organizationId: _$mongoose.Types.ObjectId | string;
381
- /** Stored lowercase-normalized (schema `lowercase: true` + repo trims). */
409
+ _id: import('mongoose').Types.ObjectId;
410
+ organizationId: import('mongoose').Types.ObjectId | string;
411
+ /**
412
+ * The CANONICAL address, as `@classytic/contact/email` defines it: trimmed,
413
+ * both sides lowercased, and the domain IDNA-folded to punycode. The
414
+ * repository canonicalises before every read and write — the schema's
415
+ * `lowercase: true` is a backstop for a direct model write, not the rule.
416
+ *
417
+ * `ada@bücher.de` is stored as `ada@xn--bcher-kva.de`. Two spellings of one
418
+ * host stored as two rows is a suppression that silently never matches.
419
+ */
382
420
  email: string;
383
421
  reason: SuppressionReason;
384
422
  /** Free-form origin: 'campaign:<id>', 'sequence:<id>', 'api', 'import', … */
@@ -415,12 +453,12 @@ type SubjectKind = (typeof SUBJECT_KINDS)[number];
415
453
  //#endregion
416
454
  //#region src/models/activity.schema.d.ts
417
455
  interface IActivityDocument {
418
- _id: _$mongoose.Types.ObjectId;
419
- organizationId: _$mongoose.Types.ObjectId | string;
456
+ _id: import('mongoose').Types.ObjectId;
457
+ organizationId: import('mongoose').Types.ObjectId | string;
420
458
  type: ActivityType;
421
459
  status: ActivityStatus;
422
460
  subjectKind: SubjectKind;
423
- subjectId: _$mongoose.Types.ObjectId | string;
461
+ subjectId: import('mongoose').Types.ObjectId | string;
424
462
  title: string;
425
463
  description?: string;
426
464
  ownerId?: string;
@@ -437,10 +475,10 @@ declare function createActivityModel(connection: Connection, config: SchemaBuild
437
475
  //#endregion
438
476
  //#region src/models/note.schema.d.ts
439
477
  interface INoteDocument {
440
- _id: _$mongoose.Types.ObjectId;
441
- organizationId: _$mongoose.Types.ObjectId | string;
478
+ _id: import('mongoose').Types.ObjectId;
479
+ organizationId: import('mongoose').Types.ObjectId | string;
442
480
  subjectKind: SubjectKind;
443
- subjectId: _$mongoose.Types.ObjectId | string;
481
+ subjectId: import('mongoose').Types.ObjectId | string;
444
482
  body: string;
445
483
  authorId?: string;
446
484
  pinned: boolean;
@@ -453,8 +491,8 @@ declare function createNoteModel(connection: Connection, config: SchemaBuildConf
453
491
  //#endregion
454
492
  //#region src/models/loss-reason.schema.d.ts
455
493
  interface ILossReasonDocument {
456
- _id: _$mongoose.Types.ObjectId;
457
- organizationId: _$mongoose.Types.ObjectId | string;
494
+ _id: import('mongoose').Types.ObjectId;
495
+ organizationId: import('mongoose').Types.ObjectId | string;
458
496
  name: string;
459
497
  description?: string;
460
498
  isActive: boolean;
@@ -501,12 +539,12 @@ type EmailEventKind = (typeof EMAIL_EVENT_KINDS)[number];
501
539
  declare const EMAIL_EVENT_SUBJECT_KINDS: readonly ["contact", "subscription"];
502
540
  type EmailEventSubjectKind = (typeof EMAIL_EVENT_SUBJECT_KINDS)[number];
503
541
  interface IEmailEventDocument {
504
- _id: _$mongoose.Types.ObjectId;
505
- organizationId: _$mongoose.Types.ObjectId | string;
542
+ _id: import('mongoose').Types.ObjectId;
543
+ organizationId: import('mongoose').Types.ObjectId | string;
506
544
  kind: EmailEventKind;
507
545
  subjectKind: EmailEventSubjectKind;
508
- subjectId: _$mongoose.Types.ObjectId | string;
509
- campaignId?: _$mongoose.Types.ObjectId | string;
546
+ subjectId: import('mongoose').Types.ObjectId | string;
547
+ campaignId?: import('mongoose').Types.ObjectId | string;
510
548
  messageId?: string;
511
549
  url?: string;
512
550
  userAgent?: string;
@@ -540,11 +578,11 @@ type MessageDeliveryStatus = (typeof MESSAGE_DELIVERY_STATUSES)[number];
540
578
  //#endregion
541
579
  //#region src/models/conversation.schema.d.ts
542
580
  interface IConversationDocument {
543
- _id: _$mongoose.Types.ObjectId;
544
- organizationId: _$mongoose.Types.ObjectId | string;
581
+ _id: import('mongoose').Types.ObjectId;
582
+ organizationId: import('mongoose').Types.ObjectId | string;
545
583
  channel: ConversationChannel;
546
584
  participantKind: ConversationParticipantKind;
547
- participantId: _$mongoose.Types.ObjectId | string;
585
+ participantId: import('mongoose').Types.ObjectId | string;
548
586
  /** Channel-side identifier (e.g. WhatsApp wa_id, Telegram chat id). */
549
587
  channelUserId?: string;
550
588
  status: ConversationStatus;
@@ -571,9 +609,9 @@ interface IMessageAttachment {
571
609
  mimeType?: string;
572
610
  }
573
611
  interface IMessageDocument {
574
- _id: _$mongoose.Types.ObjectId;
575
- organizationId: _$mongoose.Types.ObjectId | string;
576
- conversationId: _$mongoose.Types.ObjectId | string;
612
+ _id: import('mongoose').Types.ObjectId;
613
+ organizationId: import('mongoose').Types.ObjectId | string;
614
+ conversationId: import('mongoose').Types.ObjectId | string;
577
615
  direction: MessageDirection;
578
616
  body: string;
579
617
  attachments: IMessageAttachment[];
@@ -635,8 +673,8 @@ interface ISequenceStatusEntry {
635
673
  note?: string;
636
674
  }
637
675
  interface ISequenceDocument {
638
- _id: _$mongoose.Types.ObjectId;
639
- organizationId: _$mongoose.Types.ObjectId | string;
676
+ _id: import('mongoose').Types.ObjectId;
677
+ organizationId: import('mongoose').Types.ObjectId | string;
640
678
  name: string;
641
679
  description?: string;
642
680
  status: SequenceStatus;
@@ -687,11 +725,11 @@ interface IEnrollmentStatusEntry {
687
725
  by?: string;
688
726
  }
689
727
  interface IEnrollmentDocument {
690
- _id: _$mongoose.Types.ObjectId;
691
- organizationId: _$mongoose.Types.ObjectId | string;
692
- sequenceId: _$mongoose.Types.ObjectId | string;
728
+ _id: import('mongoose').Types.ObjectId;
729
+ organizationId: import('mongoose').Types.ObjectId | string;
730
+ sequenceId: import('mongoose').Types.ObjectId | string;
693
731
  subjectKind: EnrollmentSubjectKind;
694
- subjectId: _$mongoose.Types.ObjectId | string;
732
+ subjectId: import('mongoose').Types.ObjectId | string;
695
733
  status: EnrollmentStatus;
696
734
  /** Index into sequence.steps[] — 0-based. */
697
735
  currentStepIndex: number;
@@ -730,19 +768,27 @@ interface CrmModels {
730
768
  Sequence: Model<ISequenceDocument>;
731
769
  Enrollment: Model<IEnrollmentDocument>;
732
770
  }
733
- /** Mongoose model names this package registers — drives the §21 collision check. */
771
+ type ModularCrmModels = Partial<CrmModels>;
772
+ /** Mongoose model names this package can register (before `modelPrefix`). Reference/documentation. */
734
773
  declare const CRM_MODEL_NAMES: readonly ["Pipeline", "Lead", "Opportunity", "Contact", "Account", "Campaign", "EmailSubscription", "Suppression", "Activity", "Note", "LossReason", "EmailEvent", "Conversation", "Message", "Sequence", "Enrollment"];
735
- interface CreateModelsOptions extends SchemaBuildConfig {
774
+ interface DefineCrmModelsConfig extends SchemaBuildConfig {
775
+ /** Which model families to describe. A disabled family contributes no spec. */
776
+ modules: ResolvedCrmModules;
736
777
  /**
737
- * On a model-name collision: `false` (default) throws
738
- * `CrmModelCollisionError`; `true` deletes + re-registers (test
739
- * fixtures / hot reload only).
778
+ * On a model-name collision: `false` (default) uses mongokit's `throw`
779
+ * policy (`ModelCollisionError`); `true` uses `replace` (test fixtures /
780
+ * hot reload only — mongokit refuses `replace` under `NODE_ENV=production`).
740
781
  */
741
782
  forceRecreate?: boolean;
742
- /** Mongoose auto-index builds. Default `true` (dev); `false` in prod. */
783
+ /** Mongoose auto-index builds. Default `true` (dev); `false` in prod (§35). */
743
784
  autoIndex?: boolean;
744
785
  }
745
- declare function createCrmModels(connection: Connection, config: CreateModelsOptions): CrmModels;
786
+ /**
787
+ * Describe the CRM model set as a connection-free blueprint. Pure — builds no
788
+ * schema and registers nothing until `.bind(connection)`. Only the enabled
789
+ * module families contribute `ModelSpec` entries.
790
+ */
791
+ declare function defineCrmModels(config: DefineCrmModelsConfig): ModelBlueprint<ModularCrmModels>;
746
792
  //#endregion
747
793
  //#region src/repositories/pipeline.repository.d.ts
748
794
  interface PipelineRepositoryDeps {
@@ -809,6 +855,11 @@ declare class LeadRepository extends Repository<ILeadDocument> {
809
855
  * Create a Lead. Auto-scores on insert so hosts can route immediately.
810
856
  * Rejects duplicate emails inside the same tenant unless
811
857
  * `rejectDuplicates: false` was set at engine boot.
858
+ *
859
+ * Throws `LeadEmailFormatError` (400) on an email that cannot be canonicalised
860
+ * — nothing is written. The reason lives beside the helper in
861
+ * `core/email-identity.ts`, which also explains why the SCHEMA setter degrades
862
+ * where this verb refuses.
812
863
  */
813
864
  createLead(input: Partial<ILeadDocument>, ctx?: CrmContext): Promise<ILeadDocument>;
814
865
  markContacted(id: string, extras?: TransitionExtras, ctx?: CrmContext): Promise<ILeadDocument>;
@@ -845,6 +896,29 @@ declare class LeadRepository extends Repository<ILeadDocument> {
845
896
  * automatically; hosts also call this after enrichment writes.
846
897
  */
847
898
  recomputeScore(id: string, ctx?: CrmContext): Promise<ILeadDocument>;
899
+ /**
900
+ * The `converted` hop, owned by `LeadService.convert()` (the only caller
901
+ * — the four cross-repo writes make it a service concern). Kept on the
902
+ * repo so the atomic state write goes through `applyTransition` — a
903
+ * machine-guarded CAS that `$push`es the history entry instead of
904
+ * read-then-replacing the whole `statusHistory` array (no lost-update
905
+ * window). Deliberately emits NO `lead.status_changed`: `lead.converted`
906
+ * is the canonical signal for this transition and is dispatched by the
907
+ * service alongside the cross-repo refs.
908
+ */
909
+ markConverted(id: string, from: LeadStatus, patch: Partial<ILeadDocument>, entry: ILeadStatusEntry, ctx?: CrmContext): Promise<ILeadDocument>;
910
+ /**
911
+ * GDPR right-to-erasure for a Lead. Scrubs the PII fields
912
+ * (`fullName`/`firstName`/`lastName`/`email`/`phone`) in place while
913
+ * preserving non-PII aggregates (`score`, `status`, `statusHistory`,
914
+ * `engagement`, firmographic `companyName`/`source`) and the id. Sets
915
+ * `anonymizedAt` and emits `crm:lead.anonymized`.
916
+ *
917
+ * `fullName` is `required`, so it becomes the `'[redacted]'` sentinel;
918
+ * the optional PII scalars are nulled (dropping `email` out of the
919
+ * partial-unique email index). Idempotent.
920
+ */
921
+ anonymize(id: string, ctx?: CrmContext): Promise<ILeadDocument>;
848
922
  private _transition;
849
923
  /**
850
924
  * Count leads created on or after `since`. Used by dashboard KPI tiles.
@@ -937,15 +1011,21 @@ declare class OpportunityRepository extends Repository<IOpportunityDocument> {
937
1011
  /** Count opportunities with status `open`. Used by dashboard KPI tiles. */
938
1012
  countOpen(ctx?: CrmContext): Promise<number>;
939
1013
  /**
940
- * Sum `amount` on won opportunities updated within `since`.
941
- * Goes through the tenant-scoped `findAll()` — the multiTenant plugin
942
- * injects `organizationId`, so a missing ctx can't leak other tenants'
943
- * revenue (a manual `if (ctx)` filter would).
1014
+ * Sum `amount` on won opportunities updated on/after `since`, grouped by
1015
+ * currency. Returns one row per currency (descending by total) — a deal
1016
+ * book is multi-currency, so collapsing to a single total would silently
1017
+ * drop every non-top currency's revenue.
1018
+ *
1019
+ * Uses `aggregatePipeline` (a DB-side `$group`) instead of loading every
1020
+ * won opp into memory: the multiTenant + soft-delete policy hooks prepend
1021
+ * their `$match` so a missing ctx can't leak other tenants' revenue, and
1022
+ * the row count is bounded by the number of distinct currencies, not the
1023
+ * number of deals.
944
1024
  */
945
- sumWonSince(since: Date, ctx?: CrmContext): Promise<{
1025
+ sumWonSince(since: Date, ctx?: CrmContext): Promise<Array<{
1026
+ currency: string;
946
1027
  total: number;
947
- currency?: string;
948
- } | null>;
1028
+ }>>;
949
1029
  /** Latest `limit` opportunities — lightweight projection for the activity feed. */
950
1030
  findRecentForFeed(limit: number, ctx?: CrmContext): Promise<IOpportunityDocument[]>;
951
1031
  }
@@ -953,7 +1033,23 @@ declare class OpportunityRepository extends Repository<IOpportunityDocument> {
953
1033
  //#region src/repositories/contact.repository.d.ts
954
1034
  interface ContactRepositoryDeps {
955
1035
  events: CrmEventDispatcher;
1036
+ /** Reject `createContact()` when an email already exists in-tenant (default true). */
1037
+ rejectDuplicates?: boolean;
956
1038
  }
1039
+ /**
1040
+ * Optional synchronous re-point hook fired INSIDE `merge()`, BEFORE the
1041
+ * secondary is deleted — inside the caller's `ctx.session` when one is
1042
+ * passed. Use it to move the doomed secondary's FKs onto the primary
1043
+ * (EmailSubscription
1044
+ * `.contactId` is `required`, plus Opportunity `.primaryContactId`,
1045
+ * Activity/Note `.subjectId`) so the delete never orphans a required
1046
+ * reference. Throwing here aborts the merge — nothing is deleted.
1047
+ */
1048
+ type ContactMergeRepoint = (args: {
1049
+ primaryId: string;
1050
+ secondaryId: string;
1051
+ ctx?: CrmContext;
1052
+ }) => Promise<void> | void;
957
1053
  interface MergeContactInput {
958
1054
  /** Surviving contact id. Receives merged fields. */
959
1055
  primaryId: string;
@@ -965,13 +1061,30 @@ interface MergeContactInput {
965
1061
  * for the reverse. `tags` are always unioned.
966
1062
  */
967
1063
  strategy?: 'preferPrimary' | 'preferSecondary';
1064
+ /**
1065
+ * Optional FK re-point seam — see {@link ContactMergeRepoint}. Pass this
1066
+ * to atomically move the secondary's required references onto the primary
1067
+ * before it is deleted. When omitted, re-pointing stays the host's job
1068
+ * via the async `crm:contact.merged` event (the legacy contract).
1069
+ */
1070
+ repoint?: ContactMergeRepoint;
968
1071
  }
969
1072
  declare class ContactRepository extends Repository<IContactDocument> {
970
1073
  private readonly events;
1074
+ private readonly rejectDuplicates;
971
1075
  constructor(model: Model<IContactDocument>, plugins: PluginType[], deps: ContactRepositoryDeps, pagination?: PaginationConfig);
972
1076
  /**
973
1077
  * Create + emit `crm:contact.created`. Hosts call this instead of
974
1078
  * `create()` when they want the event side-effect (e.g. webhook fan-out).
1079
+ *
1080
+ * Rejects duplicate emails inside the same tenant unless
1081
+ * `rejectDuplicates: false` was set — mirrors `LeadRepository.createLead`.
1082
+ * The partial-unique `(organizationId, contact.email)` index is the
1083
+ * authoritative guard; the pre-check only closes the fast path.
1084
+ *
1085
+ * Throws `ContactEmailFormatError` (400) on an email that cannot be
1086
+ * canonicalised — nothing is written. See `core/email-identity.ts` for the
1087
+ * reason, including what it means for `LeadService.convert()`.
975
1088
  */
976
1089
  createContact(input: Partial<IContactDocument>, ctx?: CrmContext): Promise<IContactDocument>;
977
1090
  /**
@@ -997,6 +1110,22 @@ declare class ContactRepository extends Repository<IContactDocument> {
997
1110
  * from there.
998
1111
  */
999
1112
  merge(input: MergeContactInput, ctx?: CrmContext): Promise<IContactDocument>;
1113
+ /**
1114
+ * GDPR right-to-erasure. Scrubs every PII field on the contact in place —
1115
+ * the structured `name` and ALL `contact.*` channels (email,
1116
+ * personalEmail, phone, alternatePhone, whatsapp, telegram, linkedin,
1117
+ * instagram) — while preserving the id and non-PII aggregates
1118
+ * (`accountId`, `ownerId`, `tags`, timestamps) so historical rollups and
1119
+ * relationships survive. Sets `anonymizedAt` as a tombstone and emits
1120
+ * `crm:contact.anonymized`. Idempotent: a second call re-scrubs to the
1121
+ * same sentinel.
1122
+ *
1123
+ * `name.given` is `required`, so it becomes the `'[redacted]'` sentinel
1124
+ * rather than empty; every other name part and the whole `contact`
1125
+ * subdocument are cleared. Unsetting `contact.email` also drops the row
1126
+ * out of the partial-unique email index, freeing the address for reuse.
1127
+ */
1128
+ anonymize(id: string, ctx?: CrmContext): Promise<IContactDocument>;
1000
1129
  }
1001
1130
  //#endregion
1002
1131
  //#region src/repositories/account.repository.d.ts
@@ -1084,6 +1213,15 @@ interface SuppressInput {
1084
1213
  subjectId?: string;
1085
1214
  metadata?: Record<string, unknown>;
1086
1215
  }
1216
+ /**
1217
+ * The suppression key for `email`, or `null` when it cannot be canonicalised.
1218
+ *
1219
+ * Exported because it is the ONLY way a caller can distinguish "this address is on
1220
+ * the list" from "this address is not an address" — the read verbs collapse both
1221
+ * to `true` on purpose (see the fail-closed rule in the module docblock), and a
1222
+ * caller that needs the distinction should not re-derive the rule to get it.
1223
+ */
1224
+ declare function suppressionEmailKey(email: string): string | null;
1087
1225
  declare class SuppressionRepository extends Repository<ISuppressionDocument> {
1088
1226
  private readonly events;
1089
1227
  constructor(model: Model<ISuppressionDocument>, plugins: PluginType[], deps: SuppressionRepositoryDeps, pagination?: PaginationConfig);
@@ -1092,18 +1230,36 @@ declare class SuppressionRepository extends Repository<ISuppressionDocument> {
1092
1230
  * `getOrCreate` on the unique (org, email) index — of N concurrent
1093
1231
  * calls exactly one inserts. Emits `crm:suppression.added` only for
1094
1232
  * the inserting call; an existing row is returned unchanged, no event.
1233
+ *
1234
+ * Throws `SuppressionEmailFormatError` (400) on an address that cannot be
1235
+ * canonicalised — nothing is written.
1095
1236
  */
1096
1237
  suppress(email: string, input: SuppressInput, ctx?: CrmContext): Promise<ISuppressionDocument>;
1097
- /** True when `email` is on the org's suppression list. */
1238
+ /**
1239
+ * True when `email` is on the org's suppression list — **or when it cannot be
1240
+ * canonicalised at all**, which is the fail-closed half of the module rule
1241
+ * above. Never throws on a malformed address: the safe answer is "do not send",
1242
+ * and this verb's job is to produce that answer, not to police the input.
1243
+ */
1098
1244
  isSuppressed(email: string, ctx?: CrmContext): Promise<boolean>;
1099
1245
  /**
1100
- * Return the suppressed subset of `emails` (normalized) as a Set —
1101
- * ONE `$in` query, built for the campaign-fanout hot path:
1246
+ * Return the suppressed subset of `emails` as a Set — ONE `$in` query, built for
1247
+ * the campaign-fanout hot path:
1102
1248
  *
1103
1249
  * ```ts
1104
1250
  * const blocked = await suppression.filterSuppressed(recipients, ctx);
1105
- * const sendable = recipients.filter((e) => !blocked.has(e.toLowerCase()));
1251
+ * const sendable = recipients.filter((e) => !blocked.has(suppressionEmailKey(e) ?? e));
1106
1252
  * ```
1253
+ *
1254
+ * The Set is keyed CANONICALLY, so a caller must canonicalise its own recipient
1255
+ * before testing membership — matching on the raw spelling is how the punycode
1256
+ * miss came back. `suppressionEmailKey` is exported for exactly that, and returns
1257
+ * `null` for an address with no canonical form.
1258
+ *
1259
+ * An address that cannot be canonicalised is reported as BLOCKED, keyed by the
1260
+ * caller's original spelling (there is no canonical form to key it by, and the
1261
+ * caller has to be able to find it). It is not thrown: one malformed recipient
1262
+ * must not abort a fanout to thousands of valid ones.
1107
1263
  */
1108
1264
  filterSuppressed(emails: string[], ctx?: CrmContext): Promise<Set<string>>;
1109
1265
  /**
@@ -1111,6 +1267,11 @@ declare class SuppressionRepository extends Repository<ISuppressionDocument> {
1111
1267
  * `deleteMany` on the (unique) email key — emits
1112
1268
  * `crm:suppression.removed` only when a row was actually removed.
1113
1269
  * Returns whether a row was removed.
1270
+ *
1271
+ * Throws `SuppressionEmailFormatError` (400) on an address that cannot be
1272
+ * canonicalised, rather than answering `false`. This is the verb that re-enables
1273
+ * sending, and `false` would read as "already removed" when the truth is "that
1274
+ * was never an address" — an operator would close the ticket either way.
1114
1275
  */
1115
1276
  unsuppress(email: string, ctx?: CrmContext): Promise<boolean>;
1116
1277
  }
@@ -1342,56 +1503,78 @@ interface CrmLogger {
1342
1503
  error?(msg: string, meta?: Record<string, unknown>): void;
1343
1504
  debug?(msg: string, meta?: Record<string, unknown>): void;
1344
1505
  }
1345
- interface CrmConfig {
1346
- /** Mongoose connection — package creates models on this connection. */
1347
- connection: Connection;
1506
+ /**
1507
+ * DESCRIBE-time shape (Kernel Construction Standard §6.2) — everything that
1508
+ * determines the persistence SHAPE and needs no connection or I/O to inspect.
1509
+ * Passed to `defineCrm(shape)`; nothing here opens a socket or reads a secret.
1510
+ */
1511
+ interface CrmShape {
1348
1512
  /**
1349
1513
  * Tenant column shape. `'objectId'` (default) matches Better Auth orgs
1350
1514
  * and unlocks `$lookup` / `.populate()`; `'string'` matches UUID hosts.
1351
1515
  * Threaded into both schemas and `multiTenantPlugin` so they stay in
1352
1516
  * sync (PACKAGE_RULES §9.2).
1353
1517
  */
1354
- tenantFieldType?: TenantFieldType;
1518
+ tenantFieldType?: TenantFieldType | undefined;
1519
+ /**
1520
+ * Prefix for Mongoose model registry names (for example `Crm` produces
1521
+ * `CrmAccount`). Use in large hosts where generic names such as `Account`
1522
+ * are already owned by accounting or identity modules.
1523
+ */
1524
+ modelPrefix?: string | undefined;
1355
1525
  /**
1356
1526
  * When `true` (default), wire `multiTenantPlugin` on every repository
1357
1527
  * with `required: true`. Set `false` only for fixtures / smoke tests
1358
1528
  * that intentionally drop the scope.
1359
1529
  */
1360
- multiTenant?: boolean;
1361
- /** Arc-compatible event transport. Default: in-process bus. */
1362
- eventTransport?: EventTransport;
1530
+ multiTenant?: boolean | undefined;
1363
1531
  /**
1364
- * Transactional outbox (PACKAGE_RULES §P8). When wired, every domain
1365
- * event is persisted via `outbox.save(event, { session })` in the SAME
1366
- * session as the business write — the host's relay publishes committed
1367
- * rows. Without it, events publish post-write (or defer to a
1368
- * `ctx[PENDING_EVENTS]` queue inside `engine.withTransaction`).
1532
+ * Progressive feature bundles (PACKAGE_RULES §22). Omit for the
1533
+ * all-enabled engine. When provided, unspecified modules are disabled;
1534
+ * dependencies are enabled automatically.
1369
1535
  */
1370
- outbox?: OutboxStore;
1371
- /** Logger surface used by the in-process bus + repositories. */
1372
- logger?: CrmLogger;
1373
- /** Pagination defaults for every repository (mongokit). */
1374
- pagination?: PaginationConfig;
1375
- /** Extra mongokit plugins applied to every repository (after defaults). */
1376
- plugins?: PluginType[];
1536
+ modules?: CrmModulesConfig | undefined;
1377
1537
  /**
1378
1538
  * On a Mongoose model-name collision (another engine / hot reload owns
1379
- * the name): `false` (default) throws `CrmModelCollisionError`; `true`
1380
- * deletes + re-registers. Test fixtures / hot reload only (§21).
1539
+ * the name): `false` (default) throws mongokit's `ModelCollisionError`;
1540
+ * `true` re-registers. Test fixtures / hot reload only (§21).
1381
1541
  */
1382
- forceRecreate?: boolean;
1542
+ forceRecreate?: boolean | undefined;
1383
1543
  /**
1384
1544
  * Mongoose auto-index builds. Default `true` (dev). Set `false` in
1385
1545
  * production and run `engine.syncIndexes()` from a deploy script so
1386
1546
  * cold-boot index builds don't blow Fastify's plugin timeout (§35).
1387
1547
  */
1388
- autoIndex?: boolean;
1548
+ autoIndex?: boolean | undefined;
1549
+ }
1550
+ /**
1551
+ * BIND-time runtime collaborators (Kernel Construction Standard §6.2). The live
1552
+ * connection is the first `bind` arg; these are the rest. A transport, outbox,
1553
+ * logger or plugin set belongs here, not in the shape.
1554
+ */
1555
+ interface CrmRuntime {
1556
+ /** Arc-compatible event transport. Default: in-process bus (owned + closed by the engine). */
1557
+ eventTransport?: EventTransport | undefined;
1558
+ /**
1559
+ * Transactional outbox (PACKAGE_RULES §P8). When wired, every domain
1560
+ * event is persisted via `outbox.save(event, { session })` in the SAME
1561
+ * session as the business write — the host's relay publishes committed
1562
+ * rows. Without it, events publish post-write (or defer to a
1563
+ * `ctx[PENDING_EVENTS]` queue inside `engine.withTransaction`).
1564
+ */
1565
+ outbox?: OutboxStore | undefined;
1566
+ /** Logger surface used by the in-process bus + repositories. */
1567
+ logger?: CrmLogger | undefined;
1568
+ /** Pagination defaults for every repository (mongokit). */
1569
+ pagination?: PaginationConfig | undefined;
1570
+ /** Extra mongokit plugins applied to every repository (after defaults). */
1571
+ plugins?: PluginType[] | undefined;
1389
1572
  /**
1390
1573
  * Skip the boot-time `transactions` capability requirement, downgrading
1391
1574
  * it to a warning. Set only when the host never calls
1392
1575
  * `LeadService.convert()` inside a transaction (standalone Mongo).
1393
1576
  */
1394
- allowNonTransactional?: boolean;
1577
+ allowNonTransactional?: boolean | undefined;
1395
1578
  }
1396
1579
  interface ResolvedCrmConfig extends SchemaBuildConfig {
1397
1580
  connection: Connection;
@@ -1399,8 +1582,9 @@ interface ResolvedCrmConfig extends SchemaBuildConfig {
1399
1582
  multiTenant: boolean;
1400
1583
  forceRecreate: boolean;
1401
1584
  autoIndex: boolean;
1402
- logger?: CrmLogger;
1403
- pagination?: PaginationConfig;
1585
+ modules: ResolvedCrmModules;
1586
+ logger?: CrmLogger | undefined;
1587
+ pagination?: PaginationConfig | undefined;
1404
1588
  plugins: PluginType[];
1405
1589
  }
1406
1590
  interface CrmRepositories {
@@ -1421,9 +1605,30 @@ interface CrmRepositories {
1421
1605
  sequence: SequenceRepository;
1422
1606
  enrollment: EnrollmentRepository;
1423
1607
  }
1424
- interface CrmEngine {
1608
+ type ModularCrmRepositories = Partial<CrmRepositories>;
1609
+ type SalesCrmModels = Pick<CrmModels, 'Pipeline' | 'Lead' | 'Opportunity' | 'Contact' | 'Account' | 'Activity' | 'Note' | 'LossReason'>;
1610
+ type SalesCrmRepositories = Pick<CrmRepositories, 'pipeline' | 'lead' | 'opportunity' | 'contact' | 'account' | 'activity' | 'note' | 'lossReason'>;
1611
+ type EngagementCrmModels = Pick<CrmModels, 'Subscription' | 'Suppression' | 'EmailEvent' | 'Conversation' | 'Message'>;
1612
+ type EngagementCrmRepositories = Pick<CrmRepositories, 'subscription' | 'suppression' | 'emailEvent' | 'conversation' | 'message'>;
1613
+ type AutomationCrmModels = Pick<CrmModels, 'Campaign' | 'Sequence' | 'Enrollment'>;
1614
+ type AutomationCrmRepositories = Pick<CrmRepositories, 'campaign' | 'sequence' | 'enrollment'>;
1615
+ interface CrmModuleView<TModels, TRepositories> {
1616
+ readonly models: Readonly<TModels>;
1617
+ readonly repositories: Readonly<TRepositories>;
1618
+ }
1619
+ interface ModularCrmModuleViews {
1620
+ readonly sales?: CrmModuleView<SalesCrmModels, SalesCrmRepositories> | undefined;
1621
+ readonly engagement?: CrmModuleView<EngagementCrmModels, EngagementCrmRepositories> | undefined;
1622
+ readonly automation?: CrmModuleView<AutomationCrmModels, AutomationCrmRepositories> | undefined;
1623
+ }
1624
+ interface FullCrmModuleViews {
1625
+ readonly sales: CrmModuleView<SalesCrmModels, SalesCrmRepositories>;
1626
+ readonly engagement: CrmModuleView<EngagementCrmModels, EngagementCrmRepositories>;
1627
+ readonly automation: CrmModuleView<AutomationCrmModels, AutomationCrmRepositories>;
1628
+ }
1629
+ interface CrmEngineBase<TModels, TRepositories, TModules> {
1425
1630
  /** Repositories ARE the API surface (PACKAGE_RULES §1). */
1426
- readonly repositories: Readonly<CrmRepositories>;
1631
+ readonly repositories: Readonly<TRepositories>;
1427
1632
  /**
1428
1633
  * Arc-compatible event transport (a `CrmEventDispatcher` wrapping the
1429
1634
  * configured transport — `publish`/`subscribe` behave exactly like the
@@ -1431,7 +1636,9 @@ interface CrmEngine {
1431
1636
  */
1432
1637
  readonly events: CrmEventDispatcher;
1433
1638
  /** Mongoose models — for arc adapter wiring. */
1434
- readonly models: Readonly<CrmModels>;
1639
+ readonly models: Readonly<TModels>;
1640
+ /** Capability-oriented views for hosts that opt into progressive modules. */
1641
+ readonly modules: Readonly<TModules>;
1435
1642
  /** Resolved configuration (frozen). */
1436
1643
  readonly config: Readonly<ResolvedCrmConfig>;
1437
1644
  /**
@@ -1453,8 +1660,34 @@ interface CrmEngine {
1453
1660
  * deploy when booted with `autoIndex: false` (§35). Idempotent.
1454
1661
  */
1455
1662
  syncIndexes(): Promise<void>;
1456
- /** Release resources. Safe to call multiple times. */
1457
- dispose(): Promise<void>;
1663
+ /**
1664
+ * Tear the engine down (Kernel Construction Standard §6.4 — the sole teardown
1665
+ * name). Closes the event bus ONLY when the engine created it itself
1666
+ * (`InProcessCrmBus`); a host-injected `eventTransport` is the host's to
1667
+ * close. Idempotent — a second call is a no-op. Mongoose
1668
+ * models/connection are untouched.
1669
+ */
1670
+ close(): Promise<void>;
1671
+ }
1672
+ /** All-enabled engine returned when `shape.modules` is omitted. */
1673
+ type CrmEngine = CrmEngineBase<CrmModels, CrmRepositories, FullCrmModuleViews>;
1674
+ /** Progressive engine returned when a `modules` selection is supplied. */
1675
+ type ModularCrmEngine = CrmEngineBase<ModularCrmModels, ModularCrmRepositories, ModularCrmModuleViews>;
1676
+ /**
1677
+ * The connection-free description produced by `defineCrm(shape)` (Kernel
1678
+ * Construction Standard §6.1). `defineCrm` is the SOLE construction API — there
1679
+ * is no `createCrm` wrapper. `bind` compiles the schemas on the supplied
1680
+ * connection, wires the engine, verifies capabilities, and returns the engine.
1681
+ */
1682
+ interface CrmBlueprint<TEngine extends CrmEngine | ModularCrmEngine = ModularCrmEngine> {
1683
+ readonly id: 'crm';
1684
+ /** The model names this blueprint registers — known without a connection. */
1685
+ readonly modelNames: readonly string[];
1686
+ /**
1687
+ * Compile + register the models on `connection`, wire repositories/transport,
1688
+ * verify capabilities, and return the frozen engine.
1689
+ */
1690
+ bind(connection: Connection, runtime?: CrmRuntime): TEngine;
1458
1691
  }
1459
1692
  /**
1460
1693
  * Operation context threaded through every domain verb. Extends
@@ -1463,22 +1696,28 @@ interface CrmEngine {
1463
1696
  * `metadata?` field — keep the typed surface tight.
1464
1697
  */
1465
1698
  interface CrmContext extends OperationContext {
1466
- organizationId?: string | _$mongoose.Types.ObjectId;
1467
- actorId?: string | _$mongoose.Types.ObjectId;
1699
+ organizationId?: string | import('mongoose').Types.ObjectId;
1700
+ actorId?: string | import('mongoose').Types.ObjectId;
1468
1701
  /** Mongo transaction session (passed through to mongokit). */
1469
1702
  session?: unknown;
1470
1703
  }
1471
1704
  type _ModelTypes = Model<unknown>;
1472
1705
  //#endregion
1473
- //#region src/engine/create-crm.d.ts
1474
- declare function createCrm(config: CrmConfig): Promise<CrmEngine>;
1706
+ //#region src/engine/define-crm.d.ts
1707
+ declare function defineCrm(shape: CrmShape & {
1708
+ modules: CrmModulesConfig;
1709
+ }): CrmBlueprint<ModularCrmEngine>;
1710
+ declare function defineCrm(shape?: CrmShape & {
1711
+ modules?: undefined;
1712
+ }): CrmBlueprint<CrmEngine>;
1713
+ declare function defineCrm(shape?: CrmShape): CrmBlueprint<ModularCrmEngine>;
1475
1714
  //#endregion
1476
1715
  //#region src/repositories/create-repositories.d.ts
1477
1716
  interface CreateCrmRepositoriesDeps {
1478
1717
  events: CrmEventDispatcher;
1479
1718
  config: ResolvedCrmConfig;
1480
1719
  }
1481
- declare function createCrmRepositories(models: CrmModels, deps: CreateCrmRepositoriesDeps): CrmRepositories;
1720
+ declare function createCrmRepositories(models: ModularCrmModels, deps: CreateCrmRepositoriesDeps): ModularCrmRepositories;
1482
1721
  //#endregion
1483
1722
  //#region src/services/lead.service.d.ts
1484
1723
  interface ConvertLeadInput {
@@ -1598,9 +1837,11 @@ declare const CRM_EVENTS: {
1598
1837
  readonly EMAIL_EVENT_UNSUBSCRIBED: "crm:email_event.unsubscribed";
1599
1838
  readonly LEAD_ENRICHED: "crm:lead.enriched";
1600
1839
  readonly LEAD_ASSIGNED: "crm:lead.assigned";
1840
+ readonly LEAD_ANONYMIZED: "crm:lead.anonymized";
1601
1841
  readonly CONTACT_ENRICHED: "crm:contact.enriched";
1602
1842
  readonly CONTACT_ASSIGNED: "crm:contact.assigned";
1603
1843
  readonly CONTACT_MERGED: "crm:contact.merged";
1844
+ readonly CONTACT_ANONYMIZED: "crm:contact.anonymized";
1604
1845
  readonly ACCOUNT_ENRICHED: "crm:account.enriched";
1605
1846
  readonly ACCOUNT_ASSIGNED: "crm:account.assigned";
1606
1847
  readonly ACCOUNT_MERGED: "crm:account.merged";
@@ -1666,6 +1907,10 @@ declare const LeadAssigned: CrmEventDefinition<z.ZodObject<{
1666
1907
  ownerId: z.ZodString;
1667
1908
  previousOwnerId: z.ZodOptional<z.ZodString>;
1668
1909
  }, z.core.$strip>>;
1910
+ declare const LeadAnonymized: CrmEventDefinition<z.ZodObject<{
1911
+ leadId: z.ZodString;
1912
+ fields: z.ZodArray<z.ZodString>;
1913
+ }, z.core.$strip>>;
1669
1914
  declare const AccountCreated: CrmEventDefinition<z.ZodObject<{
1670
1915
  accountId: z.ZodString;
1671
1916
  name: z.ZodString;
@@ -1716,6 +1961,10 @@ declare const ContactMerged: CrmEventDefinition<z.ZodObject<{
1716
1961
  preferSecondary: "preferSecondary";
1717
1962
  }>;
1718
1963
  }, z.core.$strip>>;
1964
+ declare const ContactAnonymized: CrmEventDefinition<z.ZodObject<{
1965
+ contactId: z.ZodString;
1966
+ fields: z.ZodArray<z.ZodString>;
1967
+ }, z.core.$strip>>;
1719
1968
  declare const OpportunityCreated: CrmEventDefinition<z.ZodObject<{
1720
1969
  opportunityId: z.ZodString;
1721
1970
  pipelineId: z.ZodString;
@@ -1984,16 +2233,10 @@ declare const crmEventDefinitions: ReadonlyArray<CrmEventDefinition>;
1984
2233
  declare function createEvent<T>(type: string, payload: T, ctx?: CrmContext, meta?: Partial<EventMeta>): DomainEvent<T>;
1985
2234
  //#endregion
1986
2235
  //#region src/events/in-process-bus.d.ts
1987
- declare class InProcessCrmBus implements EventTransport {
1988
- readonly name = "in-process-crm";
1989
- private subs;
1990
- private logger?;
2236
+ declare class InProcessCrmBus extends InProcessEventBus {
1991
2237
  constructor(options?: {
1992
2238
  logger?: CrmLogger;
1993
2239
  });
1994
- publish(event: DomainEvent): Promise<void>;
1995
- subscribe(pattern: string, handler: EventHandler): Promise<() => void>;
1996
- close(): Promise<void>;
1997
2240
  }
1998
2241
  //#endregion
1999
2242
  //#region src/engine/run-unit.d.ts
@@ -2023,27 +2266,40 @@ declare function runUnit<T>(config: RunUnitConfig, ctx: CrmContext, body: (inner
2023
2266
  * Built on `@classytic/primitives/state-machine` so the same shape lights
2024
2267
  * up across kits (mongokit's `claim()` pairing, sqlitekit follow-ups).
2025
2268
  */
2026
- declare const LEAD_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"new" | "contacted" | "qualified" | "converted" | "disqualified" | "nurturing">;
2027
- declare const OPPORTUNITY_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"open" | "won" | "lost" | "abandoned">;
2028
- declare const CAMPAIGN_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"draft" | "active" | "paused" | "completed" | "archived">;
2029
- declare const SUBSCRIPTION_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"pending" | "confirmed" | "unsubscribed" | "bounced" | "complained">;
2030
- declare const ACTIVITY_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"completed" | "planned" | "in_progress" | "cancelled">;
2031
- declare const SEQUENCE_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"draft" | "active" | "paused" | "archived">;
2032
- declare const ENROLLMENT_MACHINE: _$_classytic_primitives_state_machine0.StateMachine<"active" | "paused" | "completed" | "exited">;
2269
+ declare const LEAD_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"new" | "contacted" | "qualified" | "converted" | "disqualified" | "nurturing">;
2270
+ declare const OPPORTUNITY_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"open" | "won" | "lost" | "abandoned">;
2271
+ declare const CAMPAIGN_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"draft" | "active" | "paused" | "completed" | "archived">;
2272
+ declare const SUBSCRIPTION_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"pending" | "confirmed" | "unsubscribed" | "bounced" | "complained">;
2273
+ declare const ACTIVITY_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"completed" | "planned" | "in_progress" | "cancelled">;
2274
+ declare const SEQUENCE_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"draft" | "active" | "paused" | "archived">;
2275
+ declare const ENROLLMENT_MACHINE: import("@classytic/primitives/state-machine").StateMachine<"active" | "paused" | "completed" | "exited">;
2033
2276
  //#endregion
2034
2277
  //#region src/core/errors.d.ts
2035
2278
  /**
2036
- * CRM error vocabulary. Hosts catch `CrmError` at API edges.
2279
+ * `status` makes every CRM throwable satisfy `HttpError` from
2280
+ * `@classytic/repo-core/errors` (PACKAGE_RULES "Errors and wire envelopes"),
2281
+ * so arc's global handler serializes it natively with the right code AND the
2282
+ * right status — no per-host `errorMapper`. Without it every CRM error
2283
+ * reached the client as `arc.internal_error` / 500: `NotFoundError` answered
2284
+ * 500 instead of 404, which tells a caller "the server broke" when the truth
2285
+ * was "that lead doesn't exist".
2286
+ *
2287
+ * Defaults to 422 (well-formed request, violates a domain invariant) —
2288
+ * the common case here. Subclasses override with `readonly status = N`;
2289
+ * the subclass field initializer runs after this constructor, so it wins.
2037
2290
  */
2038
- declare class CrmError extends Error {
2291
+ declare class CrmError extends Error implements HttpError {
2039
2292
  readonly code: string;
2293
+ readonly status: number;
2040
2294
  readonly details?: Record<string, unknown>;
2041
2295
  constructor(code: string, message: string, details?: Record<string, unknown>);
2042
2296
  }
2043
2297
  declare class NotFoundError extends CrmError {
2298
+ readonly status = 404;
2044
2299
  constructor(kind: string, id: string);
2045
2300
  }
2046
2301
  declare class InvalidLeadTransitionError extends CrmError {
2302
+ readonly status = 422;
2047
2303
  constructor(details: {
2048
2304
  from: string;
2049
2305
  to: string;
@@ -2051,6 +2307,7 @@ declare class InvalidLeadTransitionError extends CrmError {
2051
2307
  });
2052
2308
  }
2053
2309
  declare class InvalidOpportunityTransitionError extends CrmError {
2310
+ readonly status = 422;
2054
2311
  constructor(details: {
2055
2312
  from: string;
2056
2313
  to: string;
@@ -2058,6 +2315,7 @@ declare class InvalidOpportunityTransitionError extends CrmError {
2058
2315
  });
2059
2316
  }
2060
2317
  declare class InvalidActivityTransitionError extends CrmError {
2318
+ readonly status = 422;
2061
2319
  constructor(details: {
2062
2320
  from: string;
2063
2321
  to: string;
@@ -2065,27 +2323,39 @@ declare class InvalidActivityTransitionError extends CrmError {
2065
2323
  });
2066
2324
  }
2067
2325
  declare class StageNotInPipelineError extends CrmError {
2326
+ readonly status = 422;
2068
2327
  constructor(details: {
2069
2328
  pipelineId: string;
2070
2329
  stageId: string;
2071
2330
  });
2072
2331
  }
2073
2332
  declare class LeadAlreadyConvertedError extends CrmError {
2333
+ readonly status = 409;
2074
2334
  constructor(leadId: string);
2075
2335
  }
2076
2336
  declare class DuplicateLeadError extends CrmError {
2337
+ readonly status = 409;
2077
2338
  constructor(details: {
2078
2339
  email?: string;
2079
2340
  phone?: string;
2080
2341
  });
2081
2342
  }
2343
+ declare class DuplicateContactError extends CrmError {
2344
+ readonly status = 409;
2345
+ constructor(details: {
2346
+ email: string;
2347
+ });
2348
+ }
2082
2349
  declare class UnmanagedSessionError extends CrmError {
2350
+ readonly status = 500;
2083
2351
  constructor(eventType?: string);
2084
2352
  }
2085
2353
  declare class EmptyPipelineError extends CrmError {
2354
+ readonly status = 422;
2086
2355
  constructor(pipelineId: string);
2087
2356
  }
2088
2357
  declare class InvalidCampaignTransitionError extends CrmError {
2358
+ readonly status = 422;
2089
2359
  constructor(details: {
2090
2360
  from: string;
2091
2361
  to: string;
@@ -2093,9 +2363,11 @@ declare class InvalidCampaignTransitionError extends CrmError {
2093
2363
  });
2094
2364
  }
2095
2365
  declare class DuplicateCampaignError extends CrmError {
2366
+ readonly status = 409;
2096
2367
  constructor(name: string);
2097
2368
  }
2098
2369
  declare class InvalidSubscriptionTransitionError extends CrmError {
2370
+ readonly status = 422;
2099
2371
  constructor(details: {
2100
2372
  from: string;
2101
2373
  to: string;
@@ -2103,18 +2375,22 @@ declare class InvalidSubscriptionTransitionError extends CrmError {
2103
2375
  });
2104
2376
  }
2105
2377
  declare class DuplicateSubscriptionError extends CrmError {
2378
+ readonly status = 409;
2106
2379
  constructor(details: {
2107
2380
  contactId: string;
2108
2381
  campaignId: string;
2109
2382
  });
2110
2383
  }
2111
2384
  declare class InvalidConfirmationTokenError extends CrmError {
2385
+ readonly status = 422;
2112
2386
  constructor();
2113
2387
  }
2114
2388
  declare class InvalidUnsubscribeTokenError extends CrmError {
2389
+ readonly status = 422;
2115
2390
  constructor();
2116
2391
  }
2117
2392
  declare class InvalidSequenceTransitionError extends CrmError {
2393
+ readonly status = 422;
2118
2394
  constructor(details: {
2119
2395
  from: string;
2120
2396
  to: string;
@@ -2122,6 +2398,7 @@ declare class InvalidSequenceTransitionError extends CrmError {
2122
2398
  });
2123
2399
  }
2124
2400
  declare class InvalidEnrollmentTransitionError extends CrmError {
2401
+ readonly status = 422;
2125
2402
  constructor(details: {
2126
2403
  from: string;
2127
2404
  to: string;
@@ -2129,11 +2406,111 @@ declare class InvalidEnrollmentTransitionError extends CrmError {
2129
2406
  });
2130
2407
  }
2131
2408
  declare class AlreadyEnrolledError extends CrmError {
2409
+ readonly status = 409;
2132
2410
  constructor(details: {
2133
2411
  sequenceId: string;
2134
2412
  subjectId: string;
2135
2413
  });
2136
2414
  }
2415
+ /**
2416
+ * An address handed to a suppression-list WRITE could not be canonicalised.
2417
+ *
2418
+ * 400, not 422 — the request itself is malformed at the field, no domain
2419
+ * invariant was consulted. This is `@classytic/contact`'s `EmailFormatError`
2420
+ * re-thrown as a CRM error so arc serializes it natively; the same shape party
2421
+ * uses for `PartyPhoneFormatError`. `contact` sits below every domain kernel
2422
+ * and knows nothing about HTTP, so the mapping has to happen here.
2423
+ *
2424
+ * Only WRITES throw. Reads fail closed instead — see
2425
+ * `suppression.repository.ts`, which writes the asymmetry down.
2426
+ */
2427
+ declare class SuppressionEmailFormatError extends CrmError {
2428
+ readonly status = 400;
2429
+ constructor(details: {
2430
+ email: string;
2431
+ reason: string;
2432
+ });
2433
+ }
2434
+ /**
2435
+ * An address handed to `LeadRepository.createLead` could not be canonicalised.
2436
+ *
2437
+ * 400, same shape and same reason as `SuppressionEmailFormatError`: the field is
2438
+ * malformed, no domain invariant was consulted. Thrown from
2439
+ * `core/email-identity.ts`, which owns the write-throws / setter-degrades
2440
+ * asymmetry and writes down why each side goes the way it does.
2441
+ */
2442
+ declare class LeadEmailFormatError extends CrmError {
2443
+ readonly status = 400;
2444
+ constructor(details: {
2445
+ email: string;
2446
+ reason: string;
2447
+ });
2448
+ }
2449
+ /** As {@link LeadEmailFormatError}, for `ContactRepository.createContact`. */
2450
+ declare class ContactEmailFormatError extends CrmError {
2451
+ readonly status = 400;
2452
+ constructor(details: {
2453
+ email: string;
2454
+ reason: string;
2455
+ });
2456
+ }
2457
+ //#endregion
2458
+ //#region src/core/email-identity.d.ts
2459
+ /**
2460
+ * WHERE the email key rule is applied inside CRM. **Not a second copy of the
2461
+ * rule** — the rule is `normalizeEmail` from `@classytic/contact/email`, imported
2462
+ * directly here as it is in `suppression.repository.ts`. What this module owns is
2463
+ * the two things a shared kernel cannot decide for us: which mongoose fields carry
2464
+ * the key, and what happens when the kernel refuses an input.
2465
+ *
2466
+ * It exists as a module rather than a copy-paste because Lead and Contact both
2467
+ * carry an email identity. Two schemas hand-rolling the same setter is the drift
2468
+ * this package already paid for once.
2469
+ *
2470
+ * ## Why the SETTER, and not only the repository verbs
2471
+ *
2472
+ * `LeadRepository.createLead` / `ContactRepository.createContact` are NOT on the
2473
+ * HTTP path. `@spinekit/crm` mounts both resources through arc's mongoose CRUD
2474
+ * adapter, so `POST /crm/leads` calls the inherited `Repository.create()` and the
2475
+ * verbs' pre-check never runs. A fix that lived only on the verbs would leave the
2476
+ * live route forking one mailbox into two rows, and the tests for the verbs would
2477
+ * be green while it did.
2478
+ *
2479
+ * The narrowest boundary EVERY write must cross is the schema field, so the key
2480
+ * rule is applied there. That is also what makes the partial-unique
2481
+ * `(organizationId, email)` index authoritative: an index can only dedup what the
2482
+ * key already folded, and `lowercase: true` does not IDNA-fold. `ada@bücher.de`
2483
+ * and `ada@xn--bcher-kva.de` are one host in two encodings — the index saw two
2484
+ * strings and admitted both, no error, one mailbox as two leads.
2485
+ *
2486
+ * Mongoose runs string setters on QUERY FILTERS as well as on writes (this is why
2487
+ * `lowercase: true` already made `getOne({ email: 'ADA@x.com' })` match a stored
2488
+ * `ada@x.com`), so the setter fixes the read side in the same stroke. The
2489
+ * repository verbs still canonicalise EXPLICITLY before querying — see
2490
+ * `requireLeadEmailKey`'s call sites. That is not redundancy for its own sake:
2491
+ * a pre-check that depends on an invisible schema option silently WIDENS the day
2492
+ * someone edits the field, and a widened filter does not fail, it matches nothing
2493
+ * and reports "no duplicate".
2494
+ *
2495
+ * ## The setter must NEVER throw, and it does not
2496
+ *
2497
+ * A throw inside a setter surfaces during query casting as an opaque `CastError`,
2498
+ * which would turn a malformed filter into a 500 on a READ. So the setter degrades
2499
+ * to the LEGACY folding (`trim().toLowerCase()`) for anything the kernel refuses —
2500
+ * exactly the behaviour those values have today, so nothing already stored is
2501
+ * re-keyed by a rule that cannot explain itself.
2502
+ *
2503
+ * Refusing is the WRITE VERBS' job, where a typed 400 can be thrown and the caller
2504
+ * is a deliberate domain call rather than a filter. Same asymmetry, same reason as
2505
+ * `suppression.repository.ts`: the safe direction differs per side.
2506
+ */
2507
+ /**
2508
+ * The canonical key for `email`, or `null` when it has none.
2509
+ *
2510
+ * Exported so a host can tell "not on file" apart from "not an address" without
2511
+ * re-deriving the rule — the same reason `suppressionEmailKey` is exported.
2512
+ */
2513
+ declare function crmEmailKey(email: string): string | null;
2137
2514
  //#endregion
2138
2515
  //#region src/shared/calculators/forecast.d.ts
2139
2516
  type OpportunityForecast = Pick<IOpportunityDocument, 'amount' | 'currency' | 'probability' | 'status' | 'expectedCloseAt' | 'stageId' | 'statusHistory'>;
@@ -2826,4 +3203,4 @@ declare namespace index_d_exports {
2826
3203
  export { AddNoteDto, CancelActivityDto, CloseLossDto, CloseWinDto, CompleteCampaignDto, ConfirmSubscriptionDto, ConvertLeadDto, CreateAccountDto, CreateCampaignDto, CreateContactDto, CreateLeadDto, CreateLossReasonDto, CreatePipelineDto, CreateSequenceInput, LeadFilterDto, MoveStageDto, OpenOpportunityDto, OpportunityFilterDto, PlanActivityDto, RecordEmailEventDto, SequenceFilterInput, StageInputDto, SubscribeDto, SuppressDto, UnsubscribeDto, UnsuppressDto, UpdateAccountDto, UpdateCampaignDto, UpdateContactDto, UpdateLeadDto, UpdateLossReasonDto, UpdateNoteDto, UpdatePipelineDto, UpdateSequenceInput, accountFilterSchema, activityFilterSchema, addNoteSchema, campaignFilterSchema, cancelActivitySchema, closeLossSchema, closeWinSchema, completeCampaignSchema, confirmSubscriptionSchema, contactFilterSchema, convertLeadSchema, createAccountSchema, createCampaignSchema, createContactSchema, createLeadSchema, createLossReasonSchema, createPipelineSchema, createSequenceSchema, currencyCodeSchema, emailEventFilterSchema, leadFilterSchema, lossReasonFilterSchema, metadataSchema, moneyAmountSchema, moveStageSchema, noteFilterSchema, objectIdLike, openOpportunitySchema, opportunityFilterSchema, pipelineFilterSchema, planActivitySchema, recordEmailEventSchema, sequenceFilterSchema, sequenceStepSchema, stageInputSchemaExport, subscribeSchema, subscriptionFilterSchema, suppressSchema, suppressionFilterSchema, tagsSchema, unsubscribeSchema, unsuppressSchema, updateAccountSchema, updateActivitySchema, updateCampaignSchema, updateContactSchema, updateLeadSchema, updateLossReasonSchema, updateNoteSchema, updatePipelineSchema, updateSequenceSchema };
2827
3204
  }
2828
3205
  //#endregion
2829
- export { ACTIVITY_MACHINE, ACTIVITY_STATUSES, ACTIVITY_TRANSITIONS, ACTIVITY_TYPES, AccountAssigned, AccountCreated, AccountEnriched, AccountMerged, AccountRepository, AccountRepositoryDeps, AccountUpdated, ActivityCancelled, ActivityCompleted, ActivityOutcomeRecorded, ActivityPlanned, ActivityRepository, ActivityRepositoryDeps, ActivityRescheduled, type ActivityStatus, type ActivityType, AlreadyEnrolledError, CAMPAIGN_MACHINE, CAMPAIGN_STATUSES, CAMPAIGN_TRANSITIONS, CONVERSATION_CHANNELS, CONVERSATION_PARTICIPANT_KINDS, CONVERSATION_STATUSES, CRM_EVENTS, CRM_MODEL_NAMES, CampaignActivated, CampaignArchived, CampaignCompleted, CampaignCreated, CampaignPaused, CampaignRepository, CampaignRepositoryDeps, CampaignResumed, type CampaignStatus, CloseInput, ContactAssigned, ContactCreated, ContactEnriched, ContactMerged, ContactRepository, ContactRepositoryDeps, ContactUpdated, ConversationAssigned, type ConversationChannel, ConversationCreated, type ConversationParticipantKind, ConversationReopened, ConversationRepository, ConversationRepositoryDeps, ConversationResolved, type ConversationStatus, ConversionRow, ConvertLeadInput, ConvertResult, CreateCrmRepositoriesDeps, CreateModelsOptions, CrmConfig, CrmContext, CrmEngine, CrmError, CrmEventDefinition, CrmEventDispatcher, type CrmEventDispatcherOptions, CrmEventName, CrmEventSchema, CrmLogger, CrmModels, CrmRepositories, DuplicateCampaignError, DuplicateLeadError, DuplicateSubscriptionError, EMAIL_EVENT_KINDS, EMAIL_EVENT_SUBJECT_KINDS, ENROLLMENT_MACHINE, ENROLLMENT_STATUSES, ENROLLMENT_SUBJECT_KINDS, ENROLLMENT_TRANSITIONS, EVENT_DERIVED_SUPPRESSION_REASONS, EmailEventBounced, EmailEventClicked, EmailEventComplained, type EmailEventKind, EmailEventOpened, EmailEventRepository, EmailEventRepositoryDeps, EmailEventSubjectKind, EmailEventUnsubscribed, EmptyPipelineError, EnrollInput, EnrollmentCompleted, EnrollmentCreated, EnrollmentExitReason, EnrollmentExited, EnrollmentPaused, EnrollmentRepository, EnrollmentRepositoryDeps, EnrollmentResumed, type EnrollmentStatus, EnrollmentStepSent, EnrollmentSubjectKind, ForecastOptions, ForecastRow, type IAccountDocument, type IActivityDocument, type ICampaignDocument, ICampaignStatusEntry, type IContactDocument, IContactInfoEmbedded, IContactName, type IConversationDocument, IDelayStep, type IEmailEventDocument, type IEnrollmentDocument, IEnrollmentStatusEntry, type ILeadDocument, ILeadEngagement, ILeadStatusEntry, type ILossReasonDocument, IMessageAttachment, type IMessageDocument, type INoteDocument, type IOpportunityDocument, IOpportunityStatusEntry, type IPipelineDocument, ISendEmailStep, type ISequenceDocument, ISequenceStatusEntry, IStageDocument, IStepResult, type ISubscriptionDocument, ISubscriptionStatusEntry, type ISuppressionDocument, InProcessCrmBus, type InternalCrmContext, InvalidActivityTransitionError, InvalidCampaignTransitionError, InvalidConfirmationTokenError, InvalidEnrollmentTransitionError, InvalidLeadTransitionError, InvalidOpportunityTransitionError, InvalidSequenceTransitionError, InvalidSubscriptionTransitionError, InvalidUnsubscribeTokenError, LEAD_MACHINE, LEAD_STATUSES, LEAD_TRANSITIONS, LeadAlreadyConvertedError, LeadAssigned, LeadConverted, LeadCreated, LeadDisqualified, LeadEngagementSignal, LeadEnriched, LeadInput, LeadRepository, LeadRepositoryDeps, LeadScored, LeadScorer, LeadService, LeadServiceDeps, type LeadStatus, LeadStatusChanged, LossReasonRepository, LossReasonRepositoryDeps, MESSAGE_DELIVERY_STATUSES, MESSAGE_DIRECTIONS, MergeAccountInput, MergeContactInput, type MessageDeliveryStatus, type MessageDirection, MessageReceived, MessageRepository, MessageRepositoryDeps, MessageSent, MoveStageInput, NotFoundError, NoteAdded, NoteRepository, NoteRepositoryDeps, OPPORTUNITY_MACHINE, OPPORTUNITY_STATUSES, OPPORTUNITY_TRANSITIONS, OpenConversationInput, OpenOpportunityInput, OpportunityAbandoned, OpportunityAmountChanged, OpportunityAssigned, OpportunityCreated, OpportunityExtended, OpportunityForecast, OpportunityLost, OpportunityRepository, OpportunityRepositoryDeps, OpportunityStageAdvanced, OpportunityStageRegressed, type OpportunityStatus, OpportunityWon, PENDING_EVENTS, PipelineArchived, PipelineCreated, PipelineRepository, PipelineRepositoryDeps, RecordEmailEventInput, RecordMessageInput, ReplySubject, ResolvedCrmConfig, type RunUnitConfig, SEQUENCE_MACHINE, SEQUENCE_STATUSES, SEQUENCE_TRANSITIONS, SUBJECT_KINDS, SUBSCRIPTION_MACHINE, SUBSCRIPTION_STATUSES, SUBSCRIPTION_TRANSITIONS, SUPPRESSION_REASONS, SchemaBuildConfig, SequenceActivated, SequenceArchived, SequenceCreated, SequencePaused, SequenceRepository, SequenceRepositoryDeps, type SequenceStatus, SequenceStep, StageNotInPipelineError, type StatusChangeEntry, type StatusHistory, StepResultStatus, type SubjectKind, SubscribeInput, SubscriptionBounced, SubscriptionComplained, SubscriptionConfirmed, SubscriptionCreated, SubscriptionRepository, SubscriptionRepositoryDeps, type SubscriptionStatus, SubscriptionUnsubscribed, SuppressInput, SuppressionAdded, type SuppressionReason, SuppressionRemoved, SuppressionRepository, SuppressionRepositoryDeps, TenantFieldType, UnmanagedSessionError, _ModelTypes, appendStatus, buildAccountSchema, buildActivitySchema, buildCampaignSchema, buildContactSchema, buildConversationSchema, buildEmailEventSchema, buildEnrollmentSchema, buildLeadSchema, buildLossReasonSchema, buildMessageSchema, buildNoteSchema, buildOpportunitySchema, buildPipelineSchema, buildSequenceSchema, buildSubscriptionSchema, buildSuppressionSchema, canTransitionActivity, canTransitionCampaign, canTransitionEnrollment, canTransitionLead, canTransitionOpportunity, canTransitionSequence, canTransitionSubscription, composeScorers, createAccountModel, createActivityModel, createCampaignModel, createContactModel, createConversationModel, createCrm, createCrmModels, createCrmRepositories, createEmailEventModel, createEvent, createLeadModel, createLossReasonModel, createMessageModel, createNoteModel, createOpportunityModel, createPipelineModel, createSubscriptionModel, crmEventDefinitions, defaultLeadScorer, flushPending, forecast, lastTransitionTo, latestEntry, pipelineConversion, runUnit, tenantFieldDef, timeInStatus, index_d_exports as validators };
3206
+ export { ACTIVITY_MACHINE, ACTIVITY_STATUSES, ACTIVITY_TRANSITIONS, ACTIVITY_TYPES, ALL_CRM_MODULES, AccountAssigned, AccountCreated, AccountEnriched, AccountMerged, AccountRepository, AccountRepositoryDeps, AccountUpdated, ActivityCancelled, ActivityCompleted, ActivityOutcomeRecorded, ActivityPlanned, ActivityRepository, ActivityRepositoryDeps, ActivityRescheduled, type ActivityStatus, type ActivityType, AlreadyEnrolledError, AutomationCrmModels, AutomationCrmRepositories, CAMPAIGN_MACHINE, CAMPAIGN_STATUSES, CAMPAIGN_TRANSITIONS, CONVERSATION_CHANNELS, CONVERSATION_PARTICIPANT_KINDS, CONVERSATION_STATUSES, CRM_EVENTS, CRM_MODEL_NAMES, CampaignActivated, CampaignArchived, CampaignCompleted, CampaignCreated, CampaignPaused, CampaignRepository, CampaignRepositoryDeps, CampaignResumed, type CampaignStatus, CloseInput, ContactAnonymized, ContactAssigned, ContactCreated, ContactEmailFormatError, ContactEnriched, ContactMergeRepoint, ContactMerged, ContactRepository, ContactRepositoryDeps, ContactUpdated, ConversationAssigned, type ConversationChannel, ConversationCreated, type ConversationParticipantKind, ConversationReopened, ConversationRepository, ConversationRepositoryDeps, ConversationResolved, type ConversationStatus, ConversionRow, ConvertLeadInput, ConvertResult, CreateCrmRepositoriesDeps, CrmBlueprint, CrmContext, CrmEngine, CrmEngineBase, CrmError, CrmEventDefinition, CrmEventDispatcher, type CrmEventDispatcherOptions, CrmEventName, CrmEventSchema, CrmLogger, CrmModels, CrmModuleView, CrmModulesConfig, CrmRepositories, CrmRuntime, CrmShape, DefineCrmModelsConfig, DuplicateCampaignError, DuplicateContactError, DuplicateLeadError, DuplicateSubscriptionError, EMAIL_EVENT_KINDS, EMAIL_EVENT_SUBJECT_KINDS, ENROLLMENT_MACHINE, ENROLLMENT_STATUSES, ENROLLMENT_SUBJECT_KINDS, ENROLLMENT_TRANSITIONS, EVENT_DERIVED_SUPPRESSION_REASONS, EmailEventBounced, EmailEventClicked, EmailEventComplained, type EmailEventKind, EmailEventOpened, EmailEventRepository, EmailEventRepositoryDeps, EmailEventSubjectKind, EmailEventUnsubscribed, EmptyPipelineError, EngagementCrmModels, EngagementCrmRepositories, EnrollInput, EnrollmentCompleted, EnrollmentCreated, EnrollmentExitReason, EnrollmentExited, EnrollmentPaused, EnrollmentRepository, EnrollmentRepositoryDeps, EnrollmentResumed, type EnrollmentStatus, EnrollmentStepSent, EnrollmentSubjectKind, ForecastOptions, ForecastRow, FullCrmModuleViews, IAccountDocument, IActivityDocument, ICampaignDocument, ICampaignStatusEntry, IContactDocument, IContactInfoEmbedded, IContactName, IConversationDocument, IDelayStep, IEmailEventDocument, IEnrollmentDocument, IEnrollmentStatusEntry, ILeadDocument, ILeadEngagement, ILeadStatusEntry, ILossReasonDocument, IMessageAttachment, IMessageDocument, INoteDocument, IOpportunityDocument, IOpportunityStatusEntry, IPipelineDocument, ISendEmailStep, ISequenceDocument, ISequenceStatusEntry, IStageDocument, IStepResult, ISubscriptionDocument, ISubscriptionStatusEntry, ISuppressionDocument, InProcessCrmBus, type InternalCrmContext, InvalidActivityTransitionError, InvalidCampaignTransitionError, InvalidConfirmationTokenError, InvalidEnrollmentTransitionError, InvalidLeadTransitionError, InvalidOpportunityTransitionError, InvalidSequenceTransitionError, InvalidSubscriptionTransitionError, InvalidUnsubscribeTokenError, LEAD_MACHINE, LEAD_STATUSES, LEAD_TRANSITIONS, LeadAlreadyConvertedError, LeadAnonymized, LeadAssigned, LeadConverted, LeadCreated, LeadDisqualified, LeadEmailFormatError, LeadEngagementSignal, LeadEnriched, LeadInput, LeadRepository, LeadRepositoryDeps, LeadScored, LeadScorer, LeadService, LeadServiceDeps, type LeadStatus, LeadStatusChanged, LossReasonRepository, LossReasonRepositoryDeps, MESSAGE_DELIVERY_STATUSES, MESSAGE_DIRECTIONS, MergeAccountInput, MergeContactInput, type MessageDeliveryStatus, type MessageDirection, MessageReceived, MessageRepository, MessageRepositoryDeps, MessageSent, ModularCrmEngine, ModularCrmModels, ModularCrmModuleViews, ModularCrmRepositories, MoveStageInput, NotFoundError, NoteAdded, NoteRepository, NoteRepositoryDeps, OPPORTUNITY_MACHINE, OPPORTUNITY_STATUSES, OPPORTUNITY_TRANSITIONS, OpenConversationInput, OpenOpportunityInput, OpportunityAbandoned, OpportunityAmountChanged, OpportunityAssigned, OpportunityCreated, OpportunityExtended, OpportunityForecast, OpportunityLost, OpportunityRepository, OpportunityRepositoryDeps, OpportunityStageAdvanced, OpportunityStageRegressed, type OpportunityStatus, OpportunityWon, PENDING_EVENTS, PipelineArchived, PipelineCreated, PipelineRepository, PipelineRepositoryDeps, RecordEmailEventInput, RecordMessageInput, ReplySubject, ResolvedCrmConfig, ResolvedCrmModules, type RunUnitConfig, SEQUENCE_MACHINE, SEQUENCE_STATUSES, SEQUENCE_TRANSITIONS, SUBJECT_KINDS, SUBSCRIPTION_MACHINE, SUBSCRIPTION_STATUSES, SUBSCRIPTION_TRANSITIONS, SUPPRESSION_REASONS, SalesCrmModels, SalesCrmRepositories, SchemaBuildConfig, SequenceActivated, SequenceArchived, SequenceCreated, SequencePaused, SequenceRepository, SequenceRepositoryDeps, type SequenceStatus, SequenceStep, StageNotInPipelineError, type StatusChangeEntry, type StatusHistory, StepResultStatus, type SubjectKind, SubscribeInput, SubscriptionBounced, SubscriptionComplained, SubscriptionConfirmed, SubscriptionCreated, SubscriptionRepository, SubscriptionRepositoryDeps, type SubscriptionStatus, SubscriptionUnsubscribed, SuppressInput, SuppressionAdded, SuppressionEmailFormatError, type SuppressionReason, SuppressionRemoved, SuppressionRepository, SuppressionRepositoryDeps, TenantFieldType, UnmanagedSessionError, _ModelTypes, appendStatus, buildAccountSchema, buildActivitySchema, buildCampaignSchema, buildContactSchema, buildConversationSchema, buildEmailEventSchema, buildEnrollmentSchema, buildLeadSchema, buildLossReasonSchema, buildMessageSchema, buildNoteSchema, buildOpportunitySchema, buildPipelineSchema, buildSequenceSchema, buildSubscriptionSchema, buildSuppressionSchema, canTransitionActivity, canTransitionCampaign, canTransitionEnrollment, canTransitionLead, canTransitionOpportunity, canTransitionSequence, canTransitionSubscription, composeScorers, createAccountModel, createActivityModel, createCampaignModel, createContactModel, createConversationModel, createCrmRepositories, createEmailEventModel, createEvent, createLeadModel, createLossReasonModel, createMessageModel, createNoteModel, createOpportunityModel, createPipelineModel, createSubscriptionModel, crmEmailKey, crmEventDefinitions, crmModelName, defaultLeadScorer, defineCrm, defineCrmModels, flushPending, forecast, lastTransitionTo, latestEntry, pipelineConversion, resolveCrmModules, runUnit, suppressionEmailKey, tenantFieldDef, timeInStatus, index_d_exports as validators };