@contractspec/example.crm-pipeline 0.0.0-canary-20260113170453

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/.turbo/turbo-build$colon$bundle.log +172 -0
  2. package/.turbo/turbo-build.log +173 -0
  3. package/CHANGELOG.md +436 -0
  4. package/LICENSE +21 -0
  5. package/README.md +139 -0
  6. package/dist/crm-pipeline.feature.d.ts +12 -0
  7. package/dist/crm-pipeline.feature.d.ts.map +1 -0
  8. package/dist/crm-pipeline.feature.js +166 -0
  9. package/dist/crm-pipeline.feature.js.map +1 -0
  10. package/dist/deal/deal.enum.d.ts +14 -0
  11. package/dist/deal/deal.enum.d.ts.map +1 -0
  12. package/dist/deal/deal.enum.js +25 -0
  13. package/dist/deal/deal.enum.js.map +1 -0
  14. package/dist/deal/deal.operation.d.ts +513 -0
  15. package/dist/deal/deal.operation.d.ts.map +1 -0
  16. package/dist/deal/deal.operation.js +270 -0
  17. package/dist/deal/deal.operation.js.map +1 -0
  18. package/dist/deal/deal.schema.d.ts +300 -0
  19. package/dist/deal/deal.schema.d.ts.map +1 -0
  20. package/dist/deal/deal.schema.js +286 -0
  21. package/dist/deal/deal.schema.js.map +1 -0
  22. package/dist/deal/deal.test-spec.d.ts +8 -0
  23. package/dist/deal/deal.test-spec.d.ts.map +1 -0
  24. package/dist/deal/deal.test-spec.js +65 -0
  25. package/dist/deal/deal.test-spec.js.map +1 -0
  26. package/dist/deal/index.d.ts +4 -0
  27. package/dist/deal/index.js +5 -0
  28. package/dist/docs/crm-pipeline.docblock.d.ts +1 -0
  29. package/dist/docs/crm-pipeline.docblock.js +100 -0
  30. package/dist/docs/crm-pipeline.docblock.js.map +1 -0
  31. package/dist/docs/index.d.ts +1 -0
  32. package/dist/docs/index.js +1 -0
  33. package/dist/entities/company.entity.d.ts +40 -0
  34. package/dist/entities/company.entity.d.ts.map +1 -0
  35. package/dist/entities/company.entity.js +63 -0
  36. package/dist/entities/company.entity.js.map +1 -0
  37. package/dist/entities/contact.entity.d.ts +44 -0
  38. package/dist/entities/contact.entity.d.ts.map +1 -0
  39. package/dist/entities/contact.entity.js +78 -0
  40. package/dist/entities/contact.entity.js.map +1 -0
  41. package/dist/entities/deal.entity.d.ts +73 -0
  42. package/dist/entities/deal.entity.d.ts.map +1 -0
  43. package/dist/entities/deal.entity.js +120 -0
  44. package/dist/entities/deal.entity.js.map +1 -0
  45. package/dist/entities/index.d.ts +15 -0
  46. package/dist/entities/index.d.ts.map +1 -0
  47. package/dist/entities/index.js +33 -0
  48. package/dist/entities/index.js.map +1 -0
  49. package/dist/entities/task.entity.d.ts +65 -0
  50. package/dist/entities/task.entity.d.ts.map +1 -0
  51. package/dist/entities/task.entity.js +129 -0
  52. package/dist/entities/task.entity.js.map +1 -0
  53. package/dist/events/contact.event.d.ts +29 -0
  54. package/dist/events/contact.event.d.ts.map +1 -0
  55. package/dist/events/contact.event.js +45 -0
  56. package/dist/events/contact.event.js.map +1 -0
  57. package/dist/events/deal.event.d.ts +111 -0
  58. package/dist/events/deal.event.d.ts.map +1 -0
  59. package/dist/events/deal.event.js +172 -0
  60. package/dist/events/deal.event.js.map +1 -0
  61. package/dist/events/index.d.ts +4 -0
  62. package/dist/events/index.js +5 -0
  63. package/dist/events/task.event.d.ts +29 -0
  64. package/dist/events/task.event.d.ts.map +1 -0
  65. package/dist/events/task.event.js +45 -0
  66. package/dist/events/task.event.js.map +1 -0
  67. package/dist/example.d.ts +7 -0
  68. package/dist/example.d.ts.map +1 -0
  69. package/dist/example.js +53 -0
  70. package/dist/example.js.map +1 -0
  71. package/dist/handlers/crm.handlers.d.ts +89 -0
  72. package/dist/handlers/crm.handlers.d.ts.map +1 -0
  73. package/dist/handlers/crm.handlers.js +172 -0
  74. package/dist/handlers/crm.handlers.js.map +1 -0
  75. package/dist/handlers/deal.handlers.d.ts +94 -0
  76. package/dist/handlers/deal.handlers.d.ts.map +1 -0
  77. package/dist/handlers/deal.handlers.js +120 -0
  78. package/dist/handlers/deal.handlers.js.map +1 -0
  79. package/dist/handlers/index.d.ts +4 -0
  80. package/dist/handlers/index.js +5 -0
  81. package/dist/handlers/mock-data.d.ts +49 -0
  82. package/dist/handlers/mock-data.d.ts.map +1 -0
  83. package/dist/handlers/mock-data.js +188 -0
  84. package/dist/handlers/mock-data.js.map +1 -0
  85. package/dist/index.d.ts +47 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +56 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/operations/index.d.ts +5 -0
  90. package/dist/operations/index.js +6 -0
  91. package/dist/presentations/dashboard.presentation.d.ts +14 -0
  92. package/dist/presentations/dashboard.presentation.d.ts.map +1 -0
  93. package/dist/presentations/dashboard.presentation.js +62 -0
  94. package/dist/presentations/dashboard.presentation.js.map +1 -0
  95. package/dist/presentations/index.d.ts +3 -0
  96. package/dist/presentations/index.js +4 -0
  97. package/dist/presentations/pipeline.presentation.d.ts +22 -0
  98. package/dist/presentations/pipeline.presentation.d.ts.map +1 -0
  99. package/dist/presentations/pipeline.presentation.js +122 -0
  100. package/dist/presentations/pipeline.presentation.js.map +1 -0
  101. package/dist/seeders/index.d.ts +10 -0
  102. package/dist/seeders/index.d.ts.map +1 -0
  103. package/dist/seeders/index.js +47 -0
  104. package/dist/seeders/index.js.map +1 -0
  105. package/dist/shared/overlay-types.d.ts +34 -0
  106. package/dist/shared/overlay-types.d.ts.map +1 -0
  107. package/dist/shared/overlay-types.js +0 -0
  108. package/dist/ui/CrmDashboard.d.ts +7 -0
  109. package/dist/ui/CrmDashboard.d.ts.map +1 -0
  110. package/dist/ui/CrmDashboard.js +304 -0
  111. package/dist/ui/CrmDashboard.js.map +1 -0
  112. package/dist/ui/CrmDealCard.d.ts +15 -0
  113. package/dist/ui/CrmDealCard.d.ts.map +1 -0
  114. package/dist/ui/CrmDealCard.js +49 -0
  115. package/dist/ui/CrmDealCard.js.map +1 -0
  116. package/dist/ui/CrmPipelineBoard.d.ts +23 -0
  117. package/dist/ui/CrmPipelineBoard.d.ts.map +1 -0
  118. package/dist/ui/CrmPipelineBoard.js +98 -0
  119. package/dist/ui/CrmPipelineBoard.js.map +1 -0
  120. package/dist/ui/hooks/index.d.ts +3 -0
  121. package/dist/ui/hooks/index.js +6 -0
  122. package/dist/ui/hooks/useDealList.d.ts +35 -0
  123. package/dist/ui/hooks/useDealList.d.ts.map +1 -0
  124. package/dist/ui/hooks/useDealList.js +94 -0
  125. package/dist/ui/hooks/useDealList.js.map +1 -0
  126. package/dist/ui/hooks/useDealMutations.d.ts +26 -0
  127. package/dist/ui/hooks/useDealMutations.d.ts.map +1 -0
  128. package/dist/ui/hooks/useDealMutations.js +159 -0
  129. package/dist/ui/hooks/useDealMutations.js.map +1 -0
  130. package/dist/ui/index.d.ts +14 -0
  131. package/dist/ui/index.js +15 -0
  132. package/dist/ui/modals/CreateDealModal.d.ts +33 -0
  133. package/dist/ui/modals/CreateDealModal.d.ts.map +1 -0
  134. package/dist/ui/modals/CreateDealModal.js +183 -0
  135. package/dist/ui/modals/CreateDealModal.js.map +1 -0
  136. package/dist/ui/modals/DealActionsModal.d.ts +51 -0
  137. package/dist/ui/modals/DealActionsModal.d.ts.map +1 -0
  138. package/dist/ui/modals/DealActionsModal.js +372 -0
  139. package/dist/ui/modals/DealActionsModal.js.map +1 -0
  140. package/dist/ui/modals/index.d.ts +3 -0
  141. package/dist/ui/modals/index.js +4 -0
  142. package/dist/ui/overlays/demo-overlays.d.ts +19 -0
  143. package/dist/ui/overlays/demo-overlays.d.ts.map +1 -0
  144. package/dist/ui/overlays/demo-overlays.js +68 -0
  145. package/dist/ui/overlays/demo-overlays.js.map +1 -0
  146. package/dist/ui/overlays/index.d.ts +2 -0
  147. package/dist/ui/overlays/index.js +3 -0
  148. package/dist/ui/renderers/index.d.ts +3 -0
  149. package/dist/ui/renderers/index.js +4 -0
  150. package/dist/ui/renderers/pipeline.markdown.d.ts +23 -0
  151. package/dist/ui/renderers/pipeline.markdown.d.ts.map +1 -0
  152. package/dist/ui/renderers/pipeline.markdown.js +118 -0
  153. package/dist/ui/renderers/pipeline.markdown.js.map +1 -0
  154. package/dist/ui/renderers/pipeline.renderer.d.ts +9 -0
  155. package/dist/ui/renderers/pipeline.renderer.d.ts.map +1 -0
  156. package/dist/ui/renderers/pipeline.renderer.js +28 -0
  157. package/dist/ui/renderers/pipeline.renderer.js.map +1 -0
  158. package/example.ts +1 -0
  159. package/package.json +127 -0
  160. package/src/crm-pipeline.feature.ts +100 -0
  161. package/src/deal/deal.enum.ts +21 -0
  162. package/src/deal/deal.operation.ts +291 -0
  163. package/src/deal/deal.schema.ts +154 -0
  164. package/src/deal/deal.test-spec.ts +55 -0
  165. package/src/deal/index.ts +26 -0
  166. package/src/docs/crm-pipeline.docblock.ts +98 -0
  167. package/src/docs/index.ts +1 -0
  168. package/src/entities/company.entity.ts +77 -0
  169. package/src/entities/contact.entity.ts +93 -0
  170. package/src/entities/deal.entity.ts +160 -0
  171. package/src/entities/index.ts +45 -0
  172. package/src/entities/task.entity.ts +137 -0
  173. package/src/events/contact.event.ts +31 -0
  174. package/src/events/deal.event.ts +104 -0
  175. package/src/events/index.ts +3 -0
  176. package/src/events/task.event.ts +28 -0
  177. package/src/example.ts +38 -0
  178. package/src/handlers/crm.handlers.ts +415 -0
  179. package/src/handlers/deal.handlers.ts +253 -0
  180. package/src/handlers/index.ts +30 -0
  181. package/src/handlers/mock-data.ts +198 -0
  182. package/src/index.ts +32 -0
  183. package/src/operations/index.ts +20 -0
  184. package/src/presentations/dashboard.presentation.ts +59 -0
  185. package/src/presentations/index.ts +2 -0
  186. package/src/presentations/pipeline.presentation.ts +117 -0
  187. package/src/seeders/index.ts +35 -0
  188. package/src/shared/overlay-types.ts +39 -0
  189. package/src/ui/CrmDashboard.tsx +311 -0
  190. package/src/ui/CrmDealCard.tsx +83 -0
  191. package/src/ui/CrmPipelineBoard.tsx +136 -0
  192. package/src/ui/hooks/index.ts +10 -0
  193. package/src/ui/hooks/useDealList.ts +113 -0
  194. package/src/ui/hooks/useDealMutations.ts +174 -0
  195. package/src/ui/index.ts +18 -0
  196. package/src/ui/modals/CreateDealModal.tsx +239 -0
  197. package/src/ui/modals/DealActionsModal.tsx +424 -0
  198. package/src/ui/modals/index.ts +2 -0
  199. package/src/ui/overlays/demo-overlays.ts +68 -0
  200. package/src/ui/overlays/index.ts +1 -0
  201. package/src/ui/renderers/index.ts +6 -0
  202. package/src/ui/renderers/pipeline.markdown.ts +198 -0
  203. package/src/ui/renderers/pipeline.renderer.tsx +35 -0
  204. package/tsconfig.json +10 -0
  205. package/tsconfig.tsbuildinfo +1 -0
  206. package/tsdown.config.js +7 -0
@@ -0,0 +1,93 @@
1
+ import {
2
+ defineEntity,
3
+ defineEntityEnum,
4
+ field,
5
+ index,
6
+ } from '@contractspec/lib.schema';
7
+
8
+ /**
9
+ * Contact status enum.
10
+ */
11
+ export const ContactStatusEnum = defineEntityEnum({
12
+ name: 'ContactStatus',
13
+ values: ['LEAD', 'PROSPECT', 'CUSTOMER', 'CHURNED', 'ARCHIVED'] as const,
14
+ schema: 'crm',
15
+ description: 'Status of a contact in the sales funnel.',
16
+ });
17
+
18
+ /**
19
+ * Contact entity - individual person.
20
+ */
21
+ export const ContactEntity = defineEntity({
22
+ name: 'Contact',
23
+ description: 'An individual person in the CRM.',
24
+ schema: 'crm',
25
+ map: 'contact',
26
+ fields: {
27
+ id: field.id({ description: 'Unique contact ID' }),
28
+
29
+ // Basic info
30
+ firstName: field.string({ description: 'First name' }),
31
+ lastName: field.string({ description: 'Last name' }),
32
+ email: field.email({ isOptional: true, isUnique: true }),
33
+ phone: field.string({ isOptional: true }),
34
+
35
+ // Company
36
+ companyId: field.string({
37
+ isOptional: true,
38
+ description: 'Associated company',
39
+ }),
40
+ jobTitle: field.string({ isOptional: true }),
41
+
42
+ // Status
43
+ status: field.enum('ContactStatus', { default: 'LEAD' }),
44
+
45
+ // Ownership
46
+ organizationId: field.foreignKey(),
47
+ ownerId: field.foreignKey({
48
+ description: 'Sales rep who owns this contact',
49
+ }),
50
+
51
+ // Source
52
+ source: field.string({ isOptional: true, description: 'Lead source' }),
53
+
54
+ // Social
55
+ linkedInUrl: field.url({ isOptional: true }),
56
+ twitterHandle: field.string({ isOptional: true }),
57
+
58
+ // Address
59
+ address: field.string({ isOptional: true }),
60
+ city: field.string({ isOptional: true }),
61
+ state: field.string({ isOptional: true }),
62
+ country: field.string({ isOptional: true }),
63
+ postalCode: field.string({ isOptional: true }),
64
+
65
+ // Notes
66
+ notes: field.string({ isOptional: true }),
67
+ tags: field.string({ isArray: true }),
68
+
69
+ // Custom fields
70
+ customFields: field.json({ isOptional: true }),
71
+
72
+ // Engagement
73
+ lastContactedAt: field.dateTime({ isOptional: true }),
74
+ nextFollowUpAt: field.dateTime({ isOptional: true }),
75
+
76
+ // Timestamps
77
+ createdAt: field.createdAt(),
78
+ updatedAt: field.updatedAt(),
79
+
80
+ // Relations
81
+ company: field.belongsTo('Company', ['companyId'], ['id']),
82
+ deals: field.hasMany('Deal'),
83
+ tasks: field.hasMany('Task'),
84
+ activities: field.hasMany('Activity'),
85
+ },
86
+ indexes: [
87
+ index.on(['organizationId', 'status']),
88
+ index.on(['organizationId', 'ownerId']),
89
+ index.on(['organizationId', 'companyId']),
90
+ index.on(['email']),
91
+ ],
92
+ enums: [ContactStatusEnum],
93
+ });
@@ -0,0 +1,160 @@
1
+ import {
2
+ defineEntity,
3
+ defineEntityEnum,
4
+ field,
5
+ index,
6
+ } from '@contractspec/lib.schema';
7
+
8
+ /**
9
+ * Deal status enum.
10
+ */
11
+ export const DealStatusEnum = defineEntityEnum({
12
+ name: 'DealStatus',
13
+ values: ['OPEN', 'WON', 'LOST', 'STALE'] as const,
14
+ schema: 'crm',
15
+ description: 'Status of a deal.',
16
+ });
17
+
18
+ /**
19
+ * Pipeline entity - sales pipeline definition.
20
+ */
21
+ export const PipelineEntity = defineEntity({
22
+ name: 'Pipeline',
23
+ description: 'A sales pipeline with stages.',
24
+ schema: 'crm',
25
+ map: 'pipeline',
26
+ fields: {
27
+ id: field.id(),
28
+ name: field.string({ description: 'Pipeline name' }),
29
+ description: field.string({ isOptional: true }),
30
+
31
+ // Ownership
32
+ organizationId: field.foreignKey(),
33
+
34
+ // Settings
35
+ isDefault: field.boolean({ default: false }),
36
+
37
+ // Timestamps
38
+ createdAt: field.createdAt(),
39
+ updatedAt: field.updatedAt(),
40
+
41
+ // Relations
42
+ stages: field.hasMany('Stage'),
43
+ deals: field.hasMany('Deal'),
44
+ },
45
+ });
46
+
47
+ /**
48
+ * Stage entity - pipeline stage.
49
+ */
50
+ export const StageEntity = defineEntity({
51
+ name: 'Stage',
52
+ description: 'A stage within a sales pipeline.',
53
+ schema: 'crm',
54
+ map: 'stage',
55
+ fields: {
56
+ id: field.id(),
57
+ name: field.string({ description: 'Stage name' }),
58
+ pipelineId: field.foreignKey(),
59
+
60
+ // Position
61
+ position: field.int({ description: 'Order in pipeline' }),
62
+
63
+ // Probability
64
+ probability: field.int({
65
+ default: 0,
66
+ description: 'Win probability (0-100)',
67
+ }),
68
+
69
+ // Type
70
+ isWonStage: field.boolean({ default: false }),
71
+ isLostStage: field.boolean({ default: false }),
72
+
73
+ // Settings
74
+ color: field.string({
75
+ isOptional: true,
76
+ description: 'Stage color for UI',
77
+ }),
78
+
79
+ // Timestamps
80
+ createdAt: field.createdAt(),
81
+ updatedAt: field.updatedAt(),
82
+
83
+ // Relations
84
+ pipeline: field.belongsTo('Pipeline', ['pipelineId'], ['id'], {
85
+ onDelete: 'Cascade',
86
+ }),
87
+ deals: field.hasMany('Deal'),
88
+ },
89
+ indexes: [index.on(['pipelineId', 'position'])],
90
+ });
91
+
92
+ /**
93
+ * Deal entity - sales opportunity.
94
+ */
95
+ export const DealEntity = defineEntity({
96
+ name: 'Deal',
97
+ description: 'A sales opportunity/deal.',
98
+ schema: 'crm',
99
+ map: 'deal',
100
+ fields: {
101
+ id: field.id({ description: 'Unique deal ID' }),
102
+ name: field.string({ description: 'Deal name' }),
103
+
104
+ // Value
105
+ value: field.decimal({ description: 'Deal value' }),
106
+ currency: field.string({ default: '"USD"' }),
107
+
108
+ // Pipeline
109
+ pipelineId: field.foreignKey(),
110
+ stageId: field.foreignKey(),
111
+
112
+ // Status
113
+ status: field.enum('DealStatus', { default: 'OPEN' }),
114
+
115
+ // Associations
116
+ contactId: field.string({ isOptional: true }),
117
+ companyId: field.string({ isOptional: true }),
118
+
119
+ // Ownership
120
+ organizationId: field.foreignKey(),
121
+ ownerId: field.foreignKey({ description: 'Deal owner' }),
122
+
123
+ // Timeline
124
+ expectedCloseDate: field.dateTime({ isOptional: true }),
125
+ closedAt: field.dateTime({ isOptional: true }),
126
+
127
+ // Tracking
128
+ lostReason: field.string({ isOptional: true }),
129
+ wonSource: field.string({ isOptional: true }),
130
+
131
+ // Notes
132
+ notes: field.string({ isOptional: true }),
133
+ tags: field.string({ isArray: true }),
134
+
135
+ // Custom fields
136
+ customFields: field.json({ isOptional: true }),
137
+
138
+ // Position in stage (for Kanban)
139
+ stagePosition: field.int({ default: 0 }),
140
+
141
+ // Timestamps
142
+ createdAt: field.createdAt(),
143
+ updatedAt: field.updatedAt(),
144
+
145
+ // Relations
146
+ pipeline: field.belongsTo('Pipeline', ['pipelineId'], ['id']),
147
+ stage: field.belongsTo('Stage', ['stageId'], ['id']),
148
+ contact: field.belongsTo('Contact', ['contactId'], ['id']),
149
+ company: field.belongsTo('Company', ['companyId'], ['id']),
150
+ tasks: field.hasMany('Task'),
151
+ activities: field.hasMany('Activity'),
152
+ },
153
+ indexes: [
154
+ index.on(['organizationId', 'status']),
155
+ index.on(['pipelineId', 'stageId', 'stagePosition']),
156
+ index.on(['ownerId', 'status']),
157
+ index.on(['expectedCloseDate']),
158
+ ],
159
+ enums: [DealStatusEnum],
160
+ });
@@ -0,0 +1,45 @@
1
+ export * from './company.entity';
2
+ export * from './contact.entity';
3
+ export * from './deal.entity';
4
+ export * from './task.entity';
5
+
6
+ import type { ModuleSchemaContribution } from '@contractspec/lib.schema';
7
+ import { CompanyEntity, CompanySizeEnum } from './company.entity';
8
+ import { ContactEntity, ContactStatusEnum } from './contact.entity';
9
+ import {
10
+ DealEntity,
11
+ PipelineEntity,
12
+ StageEntity,
13
+ DealStatusEnum,
14
+ } from './deal.entity';
15
+ import {
16
+ TaskEntity,
17
+ ActivityEntity,
18
+ TaskTypeEnum,
19
+ TaskPriorityEnum,
20
+ TaskStatusEnum,
21
+ } from './task.entity';
22
+
23
+ /**
24
+ * CRM Pipeline schema contribution.
25
+ */
26
+ export const crmPipelineSchemaContribution: ModuleSchemaContribution = {
27
+ moduleId: '@contractspec/example.crm-pipeline',
28
+ entities: [
29
+ CompanyEntity,
30
+ ContactEntity,
31
+ DealEntity,
32
+ PipelineEntity,
33
+ StageEntity,
34
+ TaskEntity,
35
+ ActivityEntity,
36
+ ],
37
+ enums: [
38
+ CompanySizeEnum,
39
+ ContactStatusEnum,
40
+ DealStatusEnum,
41
+ TaskTypeEnum,
42
+ TaskPriorityEnum,
43
+ TaskStatusEnum,
44
+ ],
45
+ };
@@ -0,0 +1,137 @@
1
+ import {
2
+ defineEntity,
3
+ defineEntityEnum,
4
+ field,
5
+ index,
6
+ } from '@contractspec/lib.schema';
7
+
8
+ /**
9
+ * Task type enum.
10
+ */
11
+ export const TaskTypeEnum = defineEntityEnum({
12
+ name: 'TaskType',
13
+ values: ['CALL', 'EMAIL', 'MEETING', 'TODO', 'FOLLOW_UP', 'OTHER'] as const,
14
+ schema: 'crm',
15
+ description: 'Type of CRM task.',
16
+ });
17
+
18
+ /**
19
+ * Task priority enum.
20
+ */
21
+ export const TaskPriorityEnum = defineEntityEnum({
22
+ name: 'TaskPriority',
23
+ values: ['LOW', 'NORMAL', 'HIGH', 'URGENT'] as const,
24
+ schema: 'crm',
25
+ description: 'Priority of a task.',
26
+ });
27
+
28
+ /**
29
+ * Task status enum.
30
+ */
31
+ export const TaskStatusEnum = defineEntityEnum({
32
+ name: 'TaskStatus',
33
+ values: ['PENDING', 'IN_PROGRESS', 'COMPLETED', 'CANCELLED'] as const,
34
+ schema: 'crm',
35
+ description: 'Status of a task.',
36
+ });
37
+
38
+ /**
39
+ * Task entity - follow-up activities.
40
+ */
41
+ export const TaskEntity = defineEntity({
42
+ name: 'Task',
43
+ description: 'A task or follow-up activity.',
44
+ schema: 'crm',
45
+ map: 'task',
46
+ fields: {
47
+ id: field.id(),
48
+ title: field.string({ description: 'Task title' }),
49
+ description: field.string({ isOptional: true }),
50
+
51
+ // Type and priority
52
+ type: field.enum('TaskType', { default: 'TODO' }),
53
+ priority: field.enum('TaskPriority', { default: 'NORMAL' }),
54
+ status: field.enum('TaskStatus', { default: 'PENDING' }),
55
+
56
+ // Schedule
57
+ dueDate: field.dateTime({ isOptional: true }),
58
+ reminderAt: field.dateTime({ isOptional: true }),
59
+
60
+ // Associations (polymorphic)
61
+ contactId: field.string({ isOptional: true }),
62
+ dealId: field.string({ isOptional: true }),
63
+ companyId: field.string({ isOptional: true }),
64
+
65
+ // Ownership
66
+ organizationId: field.foreignKey(),
67
+ assignedTo: field.foreignKey({ description: 'User assigned to this task' }),
68
+ createdBy: field.foreignKey(),
69
+
70
+ // Completion
71
+ completedAt: field.dateTime({ isOptional: true }),
72
+ completedBy: field.string({ isOptional: true }),
73
+
74
+ // Timestamps
75
+ createdAt: field.createdAt(),
76
+ updatedAt: field.updatedAt(),
77
+
78
+ // Relations
79
+ contact: field.belongsTo('Contact', ['contactId'], ['id']),
80
+ deal: field.belongsTo('Deal', ['dealId'], ['id']),
81
+ company: field.belongsTo('Company', ['companyId'], ['id']),
82
+ },
83
+ indexes: [
84
+ index.on(['organizationId', 'assignedTo', 'status']),
85
+ index.on(['dueDate', 'status']),
86
+ index.on(['contactId']),
87
+ index.on(['dealId']),
88
+ ],
89
+ enums: [TaskTypeEnum, TaskPriorityEnum, TaskStatusEnum],
90
+ });
91
+
92
+ /**
93
+ * Activity entity - interaction history.
94
+ */
95
+ export const ActivityEntity = defineEntity({
96
+ name: 'Activity',
97
+ description: 'An activity/interaction logged in the CRM.',
98
+ schema: 'crm',
99
+ map: 'activity',
100
+ fields: {
101
+ id: field.id(),
102
+ type: field.enum('TaskType'),
103
+ subject: field.string(),
104
+ description: field.string({ isOptional: true }),
105
+
106
+ // Associations
107
+ contactId: field.string({ isOptional: true }),
108
+ dealId: field.string({ isOptional: true }),
109
+ companyId: field.string({ isOptional: true }),
110
+
111
+ // Ownership
112
+ organizationId: field.foreignKey(),
113
+ performedBy: field.foreignKey(),
114
+
115
+ // Outcome
116
+ outcome: field.string({ isOptional: true }),
117
+
118
+ // Timing
119
+ occurredAt: field.dateTime(),
120
+ duration: field.int({
121
+ isOptional: true,
122
+ description: 'Duration in minutes',
123
+ }),
124
+
125
+ // Timestamps
126
+ createdAt: field.createdAt(),
127
+
128
+ // Relations
129
+ contact: field.belongsTo('Contact', ['contactId'], ['id']),
130
+ deal: field.belongsTo('Deal', ['dealId'], ['id']),
131
+ company: field.belongsTo('Company', ['companyId'], ['id']),
132
+ },
133
+ indexes: [
134
+ index.on(['contactId', 'occurredAt']),
135
+ index.on(['dealId', 'occurredAt']),
136
+ ],
137
+ });
@@ -0,0 +1,31 @@
1
+ import { ScalarTypeEnum, defineSchemaModel } from '@contractspec/lib.schema';
2
+ import { defineEvent } from '@contractspec/lib.contracts';
3
+
4
+ // ============ Contact Event Payloads ============
5
+
6
+ const ContactCreatedPayload = defineSchemaModel({
7
+ name: 'ContactCreatedPayload',
8
+ description: 'Payload when a contact is created',
9
+ fields: {
10
+ contactId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
11
+ email: { type: ScalarTypeEnum.EmailAddress(), isOptional: true },
12
+ organizationId: {
13
+ type: ScalarTypeEnum.String_unsecure(),
14
+ isOptional: false,
15
+ },
16
+ ownerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
17
+ createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
18
+ },
19
+ });
20
+
21
+ export const ContactCreatedEvent = defineEvent({
22
+ meta: {
23
+ key: 'contact.created',
24
+ version: '1.0.0',
25
+ description: 'A new contact has been created.',
26
+ stability: 'stable',
27
+ owners: ['@crm-team'],
28
+ tags: ['contact', 'created'],
29
+ },
30
+ payload: ContactCreatedPayload,
31
+ });
@@ -0,0 +1,104 @@
1
+ import { ScalarTypeEnum, defineSchemaModel } from '@contractspec/lib.schema';
2
+ import { defineEvent } from '@contractspec/lib.contracts';
3
+
4
+ // ============ Deal Event Payloads ============
5
+
6
+ const DealCreatedPayload = defineSchemaModel({
7
+ name: 'DealCreatedPayload',
8
+ description: 'Payload when a deal is created',
9
+ fields: {
10
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
11
+ name: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
12
+ value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
13
+ pipelineId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
14
+ stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
15
+ ownerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
16
+ createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
17
+ },
18
+ });
19
+
20
+ const DealMovedPayload = defineSchemaModel({
21
+ name: 'DealMovedEventPayload',
22
+ description: 'Payload when a deal is moved to another stage',
23
+ fields: {
24
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
25
+ fromStageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
26
+ toStageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
27
+ movedBy: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
28
+ movedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
29
+ },
30
+ });
31
+
32
+ const DealWonPayload = defineSchemaModel({
33
+ name: 'DealWonEventPayload',
34
+ description: 'Payload when a deal is won',
35
+ fields: {
36
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
37
+ value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
38
+ currency: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
39
+ contactId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
40
+ companyId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
41
+ ownerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
42
+ wonAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
43
+ },
44
+ });
45
+
46
+ const DealLostPayload = defineSchemaModel({
47
+ name: 'DealLostEventPayload',
48
+ description: 'Payload when a deal is lost',
49
+ fields: {
50
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
51
+ value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
52
+ reason: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
53
+ ownerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
54
+ lostAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
55
+ },
56
+ });
57
+
58
+ export const DealCreatedEvent = defineEvent({
59
+ meta: {
60
+ key: 'deal.created',
61
+ version: '1.0.0',
62
+ description: 'A new deal has been created.',
63
+ stability: 'stable',
64
+ owners: ['@crm-team'],
65
+ tags: ['deal', 'created'],
66
+ },
67
+ payload: DealCreatedPayload,
68
+ });
69
+
70
+ export const DealMovedEvent = defineEvent({
71
+ meta: {
72
+ key: 'deal.moved',
73
+ version: '1.0.0',
74
+ description: 'A deal has been moved to a different stage.',
75
+ stability: 'stable',
76
+ owners: ['@crm-team'],
77
+ tags: ['deal', 'moved'],
78
+ },
79
+ payload: DealMovedPayload,
80
+ });
81
+
82
+ export const DealWonEvent = defineEvent({
83
+ meta: {
84
+ key: 'deal.won',
85
+ version: '1.0.0',
86
+ description: 'A deal has been won.',
87
+ stability: 'stable',
88
+ owners: ['@crm-team'],
89
+ tags: ['deal', 'won'],
90
+ },
91
+ payload: DealWonPayload,
92
+ });
93
+
94
+ export const DealLostEvent = defineEvent({
95
+ meta: {
96
+ key: 'deal.lost',
97
+ version: '1.0.0',
98
+ description: 'A deal has been lost.',
99
+ stability: 'stable',
100
+ owners: ['@crm-team'],
101
+ tags: ['deal', 'lost'],
102
+ },
103
+ payload: DealLostPayload,
104
+ });
@@ -0,0 +1,3 @@
1
+ export * from './contact.event';
2
+ export * from './deal.event';
3
+ export * from './task.event';
@@ -0,0 +1,28 @@
1
+ import { ScalarTypeEnum, defineSchemaModel } from '@contractspec/lib.schema';
2
+ import { defineEvent } from '@contractspec/lib.contracts';
3
+
4
+ // ============ Task Event Payloads ============
5
+
6
+ const TaskCompletedPayload = defineSchemaModel({
7
+ name: 'TaskCompletedPayload',
8
+ description: 'Payload when a task is completed',
9
+ fields: {
10
+ taskId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
11
+ type: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
12
+ assignedTo: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
13
+ completedBy: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
14
+ completedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
15
+ },
16
+ });
17
+
18
+ export const TaskCompletedEvent = defineEvent({
19
+ meta: {
20
+ key: 'task.completed',
21
+ version: '1.0.0',
22
+ description: 'A task has been completed.',
23
+ stability: 'stable',
24
+ owners: ['@crm-team'],
25
+ tags: ['task', 'lifecycle'],
26
+ },
27
+ payload: TaskCompletedPayload,
28
+ });
package/src/example.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { defineExample } from '@contractspec/lib.contracts';
2
+
3
+ const example = defineExample({
4
+ meta: {
5
+ key: 'crm-pipeline',
6
+ version: '1.0.0',
7
+ title: 'CRM Pipeline',
8
+ description:
9
+ 'Sales CRM with contacts, companies, deals, pipelines, and tasks.',
10
+ kind: 'template',
11
+ visibility: 'public',
12
+ stability: 'experimental',
13
+ owners: ['@platform.core'],
14
+ tags: ['crm', 'sales', 'pipeline', 'deals'],
15
+ },
16
+ docs: {
17
+ rootDocId: 'docs.examples.crm-pipeline',
18
+ },
19
+ entrypoints: {
20
+ packageName: '@contractspec/example.crm-pipeline',
21
+ feature: './feature',
22
+ contracts: './contracts',
23
+ presentations: './presentations',
24
+ handlers: './handlers',
25
+ docs: './docs',
26
+ },
27
+ surfaces: {
28
+ templates: true,
29
+ sandbox: {
30
+ enabled: true,
31
+ modes: ['playground', 'specs', 'builder', 'markdown', 'evolution'],
32
+ },
33
+ studio: { enabled: true, installable: true },
34
+ mcp: { enabled: true },
35
+ },
36
+ });
37
+
38
+ export default example;