@aglyn/plugins-crm 1.0.0-beta.218 → 1.0.0-beta.219

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 (249) hide show
  1. package/package.json +14 -13
  2. package/src/index.d.ts +1 -0
  3. package/src/index.js.map +1 -1
  4. package/src/lib/components/companies-bulk-bar.js +12 -4
  5. package/src/lib/components/companies-bulk-bar.js.map +1 -1
  6. package/src/lib/components/company-detail-page.js +11 -0
  7. package/src/lib/components/company-detail-page.js.map +1 -1
  8. package/src/lib/components/company-edit-drawer.js +12 -2
  9. package/src/lib/components/company-edit-drawer.js.map +1 -1
  10. package/src/lib/components/company-import-drawer.d.ts +1 -1
  11. package/src/lib/components/company-picker.js +10 -3
  12. package/src/lib/components/company-picker.js.map +1 -1
  13. package/src/lib/components/contact-detail-page.d.ts +4 -4
  14. package/src/lib/components/contact-detail-page.js +50 -53
  15. package/src/lib/components/contact-detail-page.js.map +1 -1
  16. package/src/lib/components/contact-list-columns.d.ts +3 -0
  17. package/src/lib/components/contact-list-columns.js +21 -5
  18. package/src/lib/components/contact-list-columns.js.map +1 -1
  19. package/src/lib/components/contact-merge-dialog.js +2 -1
  20. package/src/lib/components/contact-merge-dialog.js.map +1 -1
  21. package/src/lib/components/contact-timeline-card.js +4 -3
  22. package/src/lib/components/contact-timeline-card.js.map +1 -1
  23. package/src/lib/components/contacts-bulk-bar.d.ts +3 -3
  24. package/src/lib/components/contacts-bulk-bar.js +24 -51
  25. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  26. package/src/lib/components/contacts-section.js +9 -3
  27. package/src/lib/components/contacts-section.js.map +1 -1
  28. package/src/lib/components/crm-export-all-button.d.ts +1 -1
  29. package/src/lib/components/crm-export-all-button.js.map +1 -1
  30. package/src/lib/components/crm-send-email-dialog.js +9 -6
  31. package/src/lib/components/crm-send-email-dialog.js.map +1 -1
  32. package/src/lib/components/csv-import-drawer.js +2 -2
  33. package/src/lib/components/csv-import-drawer.js.map +1 -1
  34. package/src/lib/components/deal-detail-page.js +18 -2
  35. package/src/lib/components/deal-detail-page.js.map +1 -1
  36. package/src/lib/components/deal-edit-drawer.js +10 -0
  37. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  38. package/src/lib/components/deals-bulk-bar.js +13 -5
  39. package/src/lib/components/deals-bulk-bar.js.map +1 -1
  40. package/src/lib/components/lead-campaigns-card.js +6 -0
  41. package/src/lib/components/lead-campaigns-card.js.map +1 -1
  42. package/src/lib/components/lead-detail-page.js +25 -5
  43. package/src/lib/components/lead-detail-page.js.map +1 -1
  44. package/src/lib/components/lead-properties-card.js +6 -0
  45. package/src/lib/components/lead-properties-card.js.map +1 -1
  46. package/src/lib/components/lead-surfaces-note.js +4 -5
  47. package/src/lib/components/lead-surfaces-note.js.map +1 -1
  48. package/src/lib/components/lead-unqualify-dialog.js +6 -0
  49. package/src/lib/components/lead-unqualify-dialog.js.map +1 -1
  50. package/src/lib/components/leads-bulk-bar.js +28 -3
  51. package/src/lib/components/leads-bulk-bar.js.map +1 -1
  52. package/src/lib/components/leads-section.js +36 -7
  53. package/src/lib/components/leads-section.js.map +1 -1
  54. package/src/lib/components/org-lead-surfaces-note.js +6 -2
  55. package/src/lib/components/org-lead-surfaces-note.js.map +1 -1
  56. package/src/lib/components/recipes-card.js +9 -4
  57. package/src/lib/components/recipes-card.js.map +1 -1
  58. package/src/lib/components/record-sharing-card.d.ts +57 -0
  59. package/src/lib/components/record-sharing-card.js +244 -0
  60. package/src/lib/components/record-sharing-card.js.map +1 -0
  61. package/src/lib/components/settings-section.js +7 -0
  62. package/src/lib/components/settings-section.js.map +1 -1
  63. package/src/lib/components/share-records-dialog.d.ts +28 -0
  64. package/src/lib/components/share-records-dialog.js +184 -0
  65. package/src/lib/components/share-records-dialog.js.map +1 -0
  66. package/src/lib/components/sharing-rule-drawer.d.ts +37 -0
  67. package/src/lib/components/sharing-rule-drawer.js +374 -0
  68. package/src/lib/components/sharing-rule-drawer.js.map +1 -0
  69. package/src/lib/components/sharing-rules-card.d.ts +24 -0
  70. package/src/lib/components/sharing-rules-card.js +287 -0
  71. package/src/lib/components/sharing-rules-card.js.map +1 -0
  72. package/src/lib/components/use-crm-api.d.ts +1 -1
  73. package/src/lib/components/use-crm-api.js.map +1 -1
  74. package/src/lib/components/use-site-route-context.d.ts +25 -0
  75. package/src/lib/components/use-site-route-context.js +58 -0
  76. package/src/lib/components/use-site-route-context.js.map +1 -0
  77. package/src/lib/constants/api-routes.d.ts +31 -0
  78. package/src/lib/constants/api-routes.js +27 -1
  79. package/src/lib/constants/api-routes.js.map +1 -1
  80. package/src/lib/declarations.console-server.d.ts +37 -0
  81. package/src/lib/declarations.console-server.js +103 -0
  82. package/src/lib/declarations.console-server.js.map +1 -0
  83. package/src/lib/declarations.server.d.ts +6 -0
  84. package/src/lib/declarations.server.js +33 -0
  85. package/src/lib/declarations.server.js.map +1 -1
  86. package/src/lib/hooks/use-contact-remove.d.ts +35 -0
  87. package/src/lib/hooks/use-contact-remove.js +77 -0
  88. package/src/lib/hooks/use-contact-remove.js.map +1 -0
  89. package/src/lib/hooks/use-crm-sharing.d.ts +30 -0
  90. package/src/lib/hooks/use-crm-sharing.js +109 -0
  91. package/src/lib/hooks/use-crm-sharing.js.map +1 -0
  92. package/src/lib/model/companies-csv.d.ts +1 -1
  93. package/src/lib/model/companies-csv.js +2 -2
  94. package/src/lib/model/companies-csv.js.map +1 -1
  95. package/src/lib/model/contact-holder.d.ts +68 -0
  96. package/src/lib/model/contact-holder.js +129 -0
  97. package/src/lib/model/contact-holder.js.map +1 -0
  98. package/src/lib/model/contact-interaction-links.d.ts +46 -0
  99. package/src/lib/model/contact-interaction-links.js +65 -0
  100. package/src/lib/model/contact-interaction-links.js.map +1 -0
  101. package/src/lib/model/contact-merge.d.ts +136 -0
  102. package/src/lib/model/contact-merge.js +412 -0
  103. package/src/lib/model/contact-merge.js.map +1 -0
  104. package/src/lib/model/contact-record.d.ts +7 -1
  105. package/src/lib/model/contact-record.js +6 -5
  106. package/src/lib/model/contact-record.js.map +1 -1
  107. package/src/lib/model/contact-remove.d.ts +48 -0
  108. package/src/lib/model/contact-remove.js +22 -0
  109. package/src/lib/model/contact-remove.js.map +1 -0
  110. package/src/lib/model/contacts-bulk-writes.d.ts +2 -71
  111. package/src/lib/model/contacts-bulk-writes.js +2 -82
  112. package/src/lib/model/contacts-bulk-writes.js.map +1 -1
  113. package/src/lib/model/contacts-csv.d.ts +2 -2
  114. package/src/lib/model/contacts-csv.js +2 -2
  115. package/src/lib/model/contacts-csv.js.map +1 -1
  116. package/src/lib/model/crm-csv.d.ts +239 -0
  117. package/src/lib/model/crm-csv.js +467 -0
  118. package/src/lib/model/crm-csv.js.map +1 -0
  119. package/src/lib/model/crm-digest.d.ts +185 -0
  120. package/src/lib/model/crm-digest.js +272 -0
  121. package/src/lib/model/crm-digest.js.map +1 -0
  122. package/src/lib/model/crm-import.d.ts +7 -1
  123. package/src/lib/model/crm-import.js +2 -1
  124. package/src/lib/model/crm-import.js.map +1 -1
  125. package/src/lib/model/crm-record-routes.d.ts +5 -0
  126. package/src/lib/model/crm-record-routes.js +7 -0
  127. package/src/lib/model/crm-record-routes.js.map +1 -1
  128. package/src/lib/model/crm-routes.d.ts +6 -5
  129. package/src/lib/model/crm-routes.js +7 -7
  130. package/src/lib/model/crm-routes.js.map +1 -1
  131. package/src/lib/model/crm-search-sources.d.ts +15 -0
  132. package/src/lib/model/crm-search-sources.js +162 -0
  133. package/src/lib/model/crm-search-sources.js.map +1 -0
  134. package/src/lib/model/crm-sharing-api.d.ts +32 -0
  135. package/src/lib/model/crm-sharing-api.js +59 -0
  136. package/src/lib/model/crm-sharing-api.js.map +1 -0
  137. package/src/lib/model/crm-sharing.d.ts +333 -0
  138. package/src/lib/model/crm-sharing.js +603 -0
  139. package/src/lib/model/crm-sharing.js.map +1 -0
  140. package/src/lib/model/deals-csv.d.ts +1 -1
  141. package/src/lib/model/deals-csv.js +4 -5
  142. package/src/lib/model/deals-csv.js.map +1 -1
  143. package/src/lib/model/lead-filters.d.ts +1 -1
  144. package/src/lib/model/leads-csv.d.ts +1 -1
  145. package/src/lib/model/leads-csv.js +1 -1
  146. package/src/lib/model/leads-csv.js.map +1 -1
  147. package/src/lib/model/tasks-csv.d.ts +1 -1
  148. package/src/lib/model/tasks-csv.js +1 -1
  149. package/src/lib/model/tasks-csv.js.map +1 -1
  150. package/src/lib/plan-entitlements.d.ts +75 -0
  151. package/src/lib/plan-entitlements.js +56 -0
  152. package/src/lib/plan-entitlements.js.map +1 -0
  153. package/src/lib/plugin.js +3 -0
  154. package/src/lib/plugin.js.map +1 -1
  155. package/src/lib/server/api-v1/contacts-merge.d.ts +20 -0
  156. package/src/lib/server/api-v1/contacts-merge.js +120 -0
  157. package/src/lib/server/api-v1/contacts-merge.js.map +1 -0
  158. package/src/lib/server/api-v1/contacts.d.ts +18 -0
  159. package/src/lib/server/api-v1/contacts.js +1076 -0
  160. package/src/lib/server/api-v1/contacts.js.map +1 -0
  161. package/src/lib/server/api-v1/crm-activities.d.ts +18 -0
  162. package/src/lib/server/api-v1/crm-activities.js +269 -0
  163. package/src/lib/server/api-v1/crm-activities.js.map +1 -0
  164. package/src/lib/server/api-v1/crm-companies.d.ts +18 -0
  165. package/src/lib/server/api-v1/crm-companies.js +391 -0
  166. package/src/lib/server/api-v1/crm-companies.js.map +1 -0
  167. package/src/lib/server/api-v1/crm-deals.d.ts +18 -0
  168. package/src/lib/server/api-v1/crm-deals.js +631 -0
  169. package/src/lib/server/api-v1/crm-deals.js.map +1 -0
  170. package/src/lib/server/api-v1/crm-email-templates.d.ts +18 -0
  171. package/src/lib/server/api-v1/crm-email-templates.js +336 -0
  172. package/src/lib/server/api-v1/crm-email-templates.js.map +1 -0
  173. package/src/lib/server/api-v1/crm-leads.d.ts +18 -0
  174. package/src/lib/server/api-v1/crm-leads.js +832 -0
  175. package/src/lib/server/api-v1/crm-leads.js.map +1 -0
  176. package/src/lib/server/api-v1/crm-pipelines.d.ts +76 -0
  177. package/src/lib/server/api-v1/crm-pipelines.js +146 -0
  178. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -0
  179. package/src/lib/server/api-v1/crm-shared.d.ts +271 -0
  180. package/src/lib/server/api-v1/crm-shared.js +436 -0
  181. package/src/lib/server/api-v1/crm-shared.js.map +1 -0
  182. package/src/lib/server/api-v1/crm-tasks.d.ts +18 -0
  183. package/src/lib/server/api-v1/crm-tasks.js +398 -0
  184. package/src/lib/server/api-v1/crm-tasks.js.map +1 -0
  185. package/src/lib/server/api-v1/openapi.d.ts +29 -0
  186. package/src/lib/server/api-v1/openapi.js +985 -0
  187. package/src/lib/server/api-v1/openapi.js.map +1 -0
  188. package/src/lib/server/api-v1/usage.d.ts +26 -0
  189. package/src/lib/server/api-v1/usage.js +73 -0
  190. package/src/lib/server/api-v1/usage.js.map +1 -0
  191. package/src/lib/server/consent-group-participant.js +1 -1
  192. package/src/lib/server/consent-group-participant.js.map +1 -1
  193. package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
  194. package/src/lib/server/contact-lifecycle-floor.js +91 -0
  195. package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
  196. package/src/lib/server/contact-remove.d.ts +75 -0
  197. package/src/lib/server/contact-remove.js +244 -0
  198. package/src/lib/server/contact-remove.js.map +1 -0
  199. package/src/lib/server/contact-update.js +2 -1
  200. package/src/lib/server/contact-update.js.map +1 -1
  201. package/src/lib/server/contacts-import.d.ts +47 -11
  202. package/src/lib/server/contacts-import.js +171 -71
  203. package/src/lib/server/contacts-import.js.map +1 -1
  204. package/src/lib/server/contacts-merge.d.ts +1 -1
  205. package/src/lib/server/contacts-merge.js +3 -2
  206. package/src/lib/server/contacts-merge.js.map +1 -1
  207. package/src/lib/server/convert-host-lead.d.ts +189 -0
  208. package/src/lib/server/convert-host-lead.js +518 -0
  209. package/src/lib/server/convert-host-lead.js.map +1 -0
  210. package/src/lib/server/crm-sharing.d.ts +108 -0
  211. package/src/lib/server/crm-sharing.js +790 -0
  212. package/src/lib/server/crm-sharing.js.map +1 -0
  213. package/src/lib/server/daily-digest-route.d.ts +107 -0
  214. package/src/lib/server/daily-digest-route.js +588 -0
  215. package/src/lib/server/daily-digest-route.js.map +1 -0
  216. package/src/lib/server/export-route.d.ts +60 -0
  217. package/src/lib/server/export-route.js +447 -0
  218. package/src/lib/server/export-route.js.map +1 -0
  219. package/src/lib/server/inbound-route.d.ts +74 -0
  220. package/src/lib/server/inbound-route.js +257 -0
  221. package/src/lib/server/inbound-route.js.map +1 -0
  222. package/src/lib/server/lead-convert.d.ts +1 -1
  223. package/src/lib/server/lead-convert.js +2 -2
  224. package/src/lib/server/lead-convert.js.map +1 -1
  225. package/src/lib/server/merge-contacts.d.ts +55 -0
  226. package/src/lib/server/merge-contacts.js +274 -0
  227. package/src/lib/server/merge-contacts.js.map +1 -0
  228. package/src/lib/server/pipeline-record-index.d.ts +36 -0
  229. package/src/lib/server/pipeline-record-index.js +87 -0
  230. package/src/lib/server/pipeline-record-index.js.map +1 -0
  231. package/src/lib/server/record-facts.js +2 -1
  232. package/src/lib/server/record-facts.js.map +1 -1
  233. package/src/lib/server/task-reminders-route.d.ts +82 -0
  234. package/src/lib/server/task-reminders-route.js +497 -0
  235. package/src/lib/server/task-reminders-route.js.map +1 -0
  236. package/src/lib/server-deal-stage.js +2 -1
  237. package/src/lib/server-deal-stage.js.map +1 -1
  238. package/src/lib/server.d.ts +5 -2
  239. package/src/lib/server.js +47 -2
  240. package/src/lib/server.js.map +1 -1
  241. package/src/lib/testing/stand-in-site-record-routes.d.ts +26 -0
  242. package/src/lib/testing/stand-in-site-record-routes.js +55 -0
  243. package/src/lib/testing/stand-in-site-record-routes.js.map +1 -0
  244. package/src/lib/usage-axes.d.ts +35 -0
  245. package/src/lib/usage-axes.js +68 -0
  246. package/src/lib/usage-axes.js.map +1 -0
  247. package/src/lib/components/use-emails-hub-path.d.ts +0 -24
  248. package/src/lib/components/use-emails-hub-path.js +0 -59
  249. package/src/lib/components/use-emails-hub-path.js.map +0 -1
@@ -30,6 +30,11 @@
30
30
  * every facet write the console makes; see `server/contact-update.ts`.
31
31
  */ contactUpdate: 'crm/contact-update',
32
32
  /**
33
+ * `POST` — lets one or more contacts go from a site, or deletes them once
34
+ * nobody else holds them, keeping every marketing refusal they held
35
+ * (AGL-3338); see `server/contact-remove.ts`.
36
+ */ contactRemove: 'crm/contact-remove',
37
+ /**
33
38
  * `POST` — unlinks the contacts that name a company, then deletes it
34
39
  * (AGL-2804); see `server/company-delete.ts`.
35
40
  */ companyDelete: 'crm/company-delete',
@@ -52,7 +57,28 @@
52
57
  * `POST` — the workspace's email capture address (AGL-2657), minted on
53
58
  * the first ask and replaced with `rotate: true`; see
54
59
  * `server/inbound-address.ts`.
55
- */ inboundAddress: 'crm/inbound-address'
60
+ */ inboundAddress: 'crm/inbound-address',
61
+ /**
62
+ * `POST` (a run) or `GET` (the plan) — the daily digest, posted by the
63
+ * scheduler with the cron secret (AGL-2619); a machine's route, see
64
+ * `server/daily-digest-route.ts`.
65
+ */ dailyDigest: 'crm/daily-digest',
66
+ /**
67
+ * `POST` (a run) or `GET` (the plan) — the hourly task reminders, posted
68
+ * by the scheduler with the cron secret (AGL-2659); a machine's route, see
69
+ * `server/task-reminders-route.ts`.
70
+ */ taskReminders: 'crm/task-reminders',
71
+ /**
72
+ * `POST` — the mail provider's signed notice of a message received at a
73
+ * capture address (AGL-2657); a machine's route, see
74
+ * `server/inbound-route.ts`.
75
+ */ inbound: 'crm/inbound',
76
+ /**
77
+ * `GET` — one of the organization's CRM collections, or a site's leads,
78
+ * as a CSV streamed over every row (AGL-2662); the contacts and leads
79
+ * files are every workspace's (a portability route), see
80
+ * `server/export-route.ts`.
81
+ */ export: 'crm/export'
56
82
  };
57
83
  /**
58
84
  * What an organization-level activity line may point at: the CRM's record
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/api-routes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { CrmActionRecipeId } from '@aglyn/aglyn'\n\n/**\n * The CRM plugin's console API routes, as the dispatcher keys them.\n *\n * One file both halves import, because the server registers the path and\n * the client fetches it, and a route spelled twice is a route that 404s\n * the day one spelling changes. Client-safe: constants only.\n */\nexport const CRM_API_ROUTES = {\n ping: 'crm/ping',\n /**\n * `POST` — moves one contact to a lifecycle stage, or clears it; see\n * `server.ts`.\n */\n contactStage: 'crm/contact-stage',\n /**\n * `POST` — one holder's profile fields on one or more contacts (AGL-2804):\n * every facet write the console makes; see `server/contact-update.ts`.\n */\n contactUpdate: 'crm/contact-update',\n /**\n * `POST` — unlinks the contacts that name a company, then deletes it\n * (AGL-2804); see `server/company-delete.ts`.\n */\n companyDelete: 'crm/company-delete',\n /** `POST` — one email to one person from a record; see `server/email-send.ts`. */\n emailSend: 'crm/email-send',\n /**\n * `POST` — one line in the ORGANIZATION's activity feed for an act the\n * org-level hub performed client-direct (AGL-2634); see\n * `server/org-activity.ts`.\n */\n orgActivity: 'crm/org-activity',\n /**\n * `POST` — installs one recipe's action on one site (AGL-2639): the\n * server builds it, stamps it and writes it into the site's actions;\n * see `server/recipe-routes.ts`.\n */\n recipeInstall: 'crm/recipe-install',\n /**\n * `POST` — which recipes each of the organization's sites carries\n * (AGL-2639), read off the stamps; see `server/recipe-routes.ts`.\n */\n recipeStatus: 'crm/recipe-status',\n /**\n * `POST` — the workspace's email capture address (AGL-2657), minted on\n * the first ask and replaced with `rotate: true`; see\n * `server/inbound-address.ts`.\n */\n inboundAddress: 'crm/inbound-address',\n} as const\n\n/**\n * One site's answer to `crm/recipe-status` (AGL-2639).\n *\n * `installed` names the recipes a live action on the site is stamped with.\n * `unstamped` counts the live actions that carry NO stamp — every action\n * saved before the stamp existed — and a site with any is one the reader\n * cannot call free of a recipe: the card names it as \"may already have\n * it\" rather than offering the install as if to a blank site.\n */\nexport interface CrmRecipeSiteStatus {\n hostId: string\n installed: CrmActionRecipeId[]\n unstamped: number\n}\n\n/** The body `crm/recipe-install` reads, beside the route scope. */\nexport interface CrmRecipeInstallRequest {\n hostId: string\n orgId?: string\n recipeId: CrmActionRecipeId\n /** The site's form a recipe that `needs: 'form'` is keyed on. */\n formId?: string\n}\n\n/** What a successful `crm/recipe-install` answers with. */\nexport interface CrmRecipeInstallResult {\n ok: true\n actionId: string\n /** The installed action's name, as the site's Actions list will show it. */\n name: string\n recipeId: CrmActionRecipeId\n}\n\n/**\n * What an organization-level activity line may point at: the CRM's record\n * kinds, and nothing the org feed holds for its own reasons (a member, a\n * subscription). The route refuses any other type, so a bulk bar cannot\n * write a line the feed's presenter would render as a workspace event.\n */\nexport const CRM_ORG_ACTIVITY_KINDS = [\n 'contact',\n 'company',\n 'deal',\n 'lead',\n 'task',\n] as const\n\nexport type CrmOrgActivityKind = (typeof CRM_ORG_ACTIVITY_KINDS)[number]\n\n/** The most an activity line may say — a sentence, not a report. */\nexport const CRM_ORG_ACTIVITY_ACTION_MAX = 160\n\n/** The browser-side URL for a route key. */\nexport function crmApiUrl(route: keyof typeof CRM_API_ROUTES): string {\n return `/api/${CRM_API_ROUTES[route]}`\n}\n"],"names":["CRM_API_ROUTES","ping","contactStage","contactUpdate","companyDelete","emailSend","orgActivity","recipeInstall","recipeStatus","inboundAddress","CRM_ORG_ACTIVITY_KINDS","CRM_ORG_ACTIVITY_ACTION_MAX","crmApiUrl","route"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;CAMC,GACD,OAAO,MAAMA,iBAAiB;IAC5BC,MAAM;IACN;;;GAGC,GACDC,cAAc;IACd;;;GAGC,GACDC,eAAe;IACf;;;GAGC,GACDC,eAAe;IACf,gFAAgF,GAChFC,WAAW;IACX;;;;GAIC,GACDC,aAAa;IACb;;;;GAIC,GACDC,eAAe;IACf;;;GAGC,GACDC,cAAc;IACd;;;;GAIC,GACDC,gBAAgB;AAClB,EAAU;AAmCV;;;;;CAKC,GACD,OAAO,MAAMC,yBAAyB;IACpC;IACA;IACA;IACA;IACA;CACD,CAAS;AAIV,kEAAkE,GAClE,OAAO,MAAMC,8BAA8B,IAAG;AAE9C,0CAA0C,GAC1C,OAAO,SAASC,UAAUC,KAAkC;IAC1D,OAAO,CAAC,KAAK,EAAEb,cAAc,CAACa,MAAM,EAAE;AACxC"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/constants/api-routes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { CrmActionRecipeId } from '@aglyn/aglyn'\n\n/**\n * The CRM plugin's console API routes, as the dispatcher keys them.\n *\n * One file both halves import, because the server registers the path and\n * the client fetches it, and a route spelled twice is a route that 404s\n * the day one spelling changes. Client-safe: constants only.\n */\nexport const CRM_API_ROUTES = {\n ping: 'crm/ping',\n /**\n * `POST` — moves one contact to a lifecycle stage, or clears it; see\n * `server.ts`.\n */\n contactStage: 'crm/contact-stage',\n /**\n * `POST` — one holder's profile fields on one or more contacts (AGL-2804):\n * every facet write the console makes; see `server/contact-update.ts`.\n */\n contactUpdate: 'crm/contact-update',\n /**\n * `POST` — lets one or more contacts go from a site, or deletes them once\n * nobody else holds them, keeping every marketing refusal they held\n * (AGL-3338); see `server/contact-remove.ts`.\n */\n contactRemove: 'crm/contact-remove',\n /**\n * `POST` — unlinks the contacts that name a company, then deletes it\n * (AGL-2804); see `server/company-delete.ts`.\n */\n companyDelete: 'crm/company-delete',\n /** `POST` — one email to one person from a record; see `server/email-send.ts`. */\n emailSend: 'crm/email-send',\n /**\n * `POST` — one line in the ORGANIZATION's activity feed for an act the\n * org-level hub performed client-direct (AGL-2634); see\n * `server/org-activity.ts`.\n */\n orgActivity: 'crm/org-activity',\n /**\n * `POST` — installs one recipe's action on one site (AGL-2639): the\n * server builds it, stamps it and writes it into the site's actions;\n * see `server/recipe-routes.ts`.\n */\n recipeInstall: 'crm/recipe-install',\n /**\n * `POST` — which recipes each of the organization's sites carries\n * (AGL-2639), read off the stamps; see `server/recipe-routes.ts`.\n */\n recipeStatus: 'crm/recipe-status',\n /**\n * `POST` — the workspace's email capture address (AGL-2657), minted on\n * the first ask and replaced with `rotate: true`; see\n * `server/inbound-address.ts`.\n */\n inboundAddress: 'crm/inbound-address',\n /**\n * `POST` (a run) or `GET` (the plan) — the daily digest, posted by the\n * scheduler with the cron secret (AGL-2619); a machine's route, see\n * `server/daily-digest-route.ts`.\n */\n dailyDigest: 'crm/daily-digest',\n /**\n * `POST` (a run) or `GET` (the plan) — the hourly task reminders, posted\n * by the scheduler with the cron secret (AGL-2659); a machine's route, see\n * `server/task-reminders-route.ts`.\n */\n taskReminders: 'crm/task-reminders',\n /**\n * `POST` — the mail provider's signed notice of a message received at a\n * capture address (AGL-2657); a machine's route, see\n * `server/inbound-route.ts`.\n */\n inbound: 'crm/inbound',\n /**\n * `GET` — one of the organization's CRM collections, or a site's leads,\n * as a CSV streamed over every row (AGL-2662); the contacts and leads\n * files are every workspace's (a portability route), see\n * `server/export-route.ts`.\n */\n export: 'crm/export',\n} as const\n\n/**\n * One site's answer to `crm/recipe-status` (AGL-2639).\n *\n * `installed` names the recipes a live action on the site is stamped with.\n * `unstamped` counts the live actions that carry NO stamp — every action\n * saved before the stamp existed — and a site with any is one the reader\n * cannot call free of a recipe: the card names it as \"may already have\n * it\" rather than offering the install as if to a blank site.\n */\nexport interface CrmRecipeSiteStatus {\n hostId: string\n installed: CrmActionRecipeId[]\n unstamped: number\n}\n\n/** The body `crm/recipe-install` reads, beside the route scope. */\nexport interface CrmRecipeInstallRequest {\n hostId: string\n orgId?: string\n recipeId: CrmActionRecipeId\n /** The site's form a recipe that `needs: 'form'` is keyed on. */\n formId?: string\n}\n\n/** What a successful `crm/recipe-install` answers with. */\nexport interface CrmRecipeInstallResult {\n ok: true\n actionId: string\n /** The installed action's name, as the site's Actions list will show it. */\n name: string\n recipeId: CrmActionRecipeId\n}\n\n/**\n * What an organization-level activity line may point at: the CRM's record\n * kinds, and nothing the org feed holds for its own reasons (a member, a\n * subscription). The route refuses any other type, so a bulk bar cannot\n * write a line the feed's presenter would render as a workspace event.\n */\nexport const CRM_ORG_ACTIVITY_KINDS = [\n 'contact',\n 'company',\n 'deal',\n 'lead',\n 'task',\n] as const\n\nexport type CrmOrgActivityKind = (typeof CRM_ORG_ACTIVITY_KINDS)[number]\n\n/** The most an activity line may say — a sentence, not a report. */\nexport const CRM_ORG_ACTIVITY_ACTION_MAX = 160\n\n/** The browser-side URL for a route key. */\nexport function crmApiUrl(route: keyof typeof CRM_API_ROUTES): string {\n return `/api/${CRM_API_ROUTES[route]}`\n}\n"],"names":["CRM_API_ROUTES","ping","contactStage","contactUpdate","contactRemove","companyDelete","emailSend","orgActivity","recipeInstall","recipeStatus","inboundAddress","dailyDigest","taskReminders","inbound","export","CRM_ORG_ACTIVITY_KINDS","CRM_ORG_ACTIVITY_ACTION_MAX","crmApiUrl","route"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;CAMC,GACD,OAAO,MAAMA,iBAAiB;IAC5BC,MAAM;IACN;;;GAGC,GACDC,cAAc;IACd;;;GAGC,GACDC,eAAe;IACf;;;;GAIC,GACDC,eAAe;IACf;;;GAGC,GACDC,eAAe;IACf,gFAAgF,GAChFC,WAAW;IACX;;;;GAIC,GACDC,aAAa;IACb;;;;GAIC,GACDC,eAAe;IACf;;;GAGC,GACDC,cAAc;IACd;;;;GAIC,GACDC,gBAAgB;IAChB;;;;GAIC,GACDC,aAAa;IACb;;;;GAIC,GACDC,eAAe;IACf;;;;GAIC,GACDC,SAAS;IACT;;;;;GAKC,GACDC,QAAQ;AACV,EAAU;AAmCV;;;;;CAKC,GACD,OAAO,MAAMC,yBAAyB;IACpC;IACA;IACA;IACA;IACA;CACD,CAAS;AAIV,kEAAkE,GAClE,OAAO,MAAMC,8BAA8B,IAAG;AAE9C,0CAA0C,GAC1C,OAAO,SAASC,UAAUC,KAAkC;IAC1D,OAAO,CAAC,KAAK,EAAElB,cAAc,CAACkB,MAAM,EAAE;AACxC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * The CRM's CONSOLE-ONLY server declarations, named under
19
+ * `consoleServerDeclarations` in `plugins.config.json` and run at the
20
+ * console's boot.
21
+ *
22
+ * It serves the CRM's resources of `/v1` (AGL-2606): the console's router
23
+ * owns the pipeline in front of them — the key, the plan's API access, the
24
+ * quota, the rate limit, the error envelope — and hands every request under
25
+ * `/v1/contacts`, `/v1/deals` and the rest to the CRM once the key is
26
+ * authenticated and the organization's plan carries the CRM. Each resource
27
+ * brings its description for the API's OpenAPI document, and the CRM's
28
+ * records band and collection sizes join `GET /v1/usage`. The API is the
29
+ * console's alone, so the tenant runtime does not register them.
30
+ *
31
+ * Light at boot: each resource's module is imported with its first request,
32
+ * the descriptions when the document is first built, and the usage reader
33
+ * with the first usage call.
34
+ * Registering again replaces this plugin's own entries, so a second call (a
35
+ * hot reload, a spec) is harmless.
36
+ */
37
+ export declare function registerCrmConsoleServerDeclarations(): void;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ // The registry's own module, not the data layer's barrel: boot needs one
17
+ // registry, not the whole server surface.
18
+ import { registerApiV1Resource, registerApiV1UsageFigures } from "@aglyn/tenant-data-admin/server/api-v1-resources";
19
+ import { BUNDLE_ID } from "./constants/bundle-common.js";
20
+ /**
21
+ * The plan feature every CRM resource of `/v1` needs, and the sentence its
22
+ * refusal says (AGL-2611, AGL-2851): the whole CRM is included from
23
+ * Starter, so a Free workspace reads and changes none of it over the API. A
24
+ * capture still records people on every plan; working them is the CRM.
25
+ */ const CRM_SUITE = {
26
+ feature: 'crm',
27
+ message: 'The CRM — contacts, leads, companies, deals, tasks, pipelines, ' + 'activities and email templates — is not included in this organization’s plan'
28
+ };
29
+ /**
30
+ * The CRM's resources on the customer REST API, each handler loaded with the
31
+ * first request for it. In the order the API root lists them: the people
32
+ * first, then the records a team keeps about them, then a site's leads
33
+ * (`/v1/leads?siteId=`, a top-level path with the site as a parameter), then
34
+ * the letters sent from a record (AGL-2658).
35
+ */ const CRM_API_V1_RESOURCES = [
36
+ [
37
+ 'contacts',
38
+ async (...args)=>(await import("./server/api-v1/contacts.js")).handleContacts(...args)
39
+ ],
40
+ [
41
+ 'companies',
42
+ async (...args)=>(await import("./server/api-v1/crm-companies.js")).handleCompanies(...args)
43
+ ],
44
+ [
45
+ 'pipelines',
46
+ async (...args)=>(await import("./server/api-v1/crm-pipelines.js")).handlePipelines(...args)
47
+ ],
48
+ [
49
+ 'deals',
50
+ async (...args)=>(await import("./server/api-v1/crm-deals.js")).handleDeals(...args)
51
+ ],
52
+ [
53
+ 'tasks',
54
+ async (...args)=>(await import("./server/api-v1/crm-tasks.js")).handleTasks(...args)
55
+ ],
56
+ [
57
+ 'activities',
58
+ async (...args)=>(await import("./server/api-v1/crm-activities.js")).handleActivities(...args)
59
+ ],
60
+ [
61
+ 'leads',
62
+ async (...args)=>(await import("./server/api-v1/crm-leads.js")).handleLeads(...args)
63
+ ],
64
+ [
65
+ 'email-templates',
66
+ async (...args)=>(await import("./server/api-v1/crm-email-templates.js")).handleEmailTemplates(...args)
67
+ ]
68
+ ];
69
+ /**
70
+ * The CRM's CONSOLE-ONLY server declarations, named under
71
+ * `consoleServerDeclarations` in `plugins.config.json` and run at the
72
+ * console's boot.
73
+ *
74
+ * It serves the CRM's resources of `/v1` (AGL-2606): the console's router
75
+ * owns the pipeline in front of them — the key, the plan's API access, the
76
+ * quota, the rate limit, the error envelope — and hands every request under
77
+ * `/v1/contacts`, `/v1/deals` and the rest to the CRM once the key is
78
+ * authenticated and the organization's plan carries the CRM. Each resource
79
+ * brings its description for the API's OpenAPI document, and the CRM's
80
+ * records band and collection sizes join `GET /v1/usage`. The API is the
81
+ * console's alone, so the tenant runtime does not register them.
82
+ *
83
+ * Light at boot: each resource's module is imported with its first request,
84
+ * the descriptions when the document is first built, and the usage reader
85
+ * with the first usage call.
86
+ * Registering again replaces this plugin's own entries, so a second call (a
87
+ * hot reload, a spec) is harmless.
88
+ */ export function registerCrmConsoleServerDeclarations() {
89
+ for (const [resource, handle] of CRM_API_V1_RESOURCES){
90
+ registerApiV1Resource(resource, {
91
+ handle,
92
+ entitlement: CRM_SUITE,
93
+ describe: async ()=>(await import("./server/api-v1/openapi.js")).CRM_API_V1_DESCRIPTIONS[resource]
94
+ }, {
95
+ pluginId: BUNDLE_ID
96
+ });
97
+ }
98
+ registerApiV1UsageFigures(async (ctx)=>(await import("./server/api-v1/usage.js")).crmUsageFigures(ctx), {
99
+ pluginId: BUNDLE_ID
100
+ });
101
+ }
102
+
103
+ //# sourceMappingURL=declarations.console-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/declarations.console-server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The registry's own module, not the data layer's barrel: boot needs one\n// registry, not the whole server surface.\nimport {\n type ApiV1ResourceHandler,\n registerApiV1Resource,\n registerApiV1UsageFigures,\n} from '@aglyn/tenant-data-admin/server/api-v1-resources'\nimport { BUNDLE_ID } from './constants/bundle-common'\n\n/**\n * The plan feature every CRM resource of `/v1` needs, and the sentence its\n * refusal says (AGL-2611, AGL-2851): the whole CRM is included from\n * Starter, so a Free workspace reads and changes none of it over the API. A\n * capture still records people on every plan; working them is the CRM.\n */\nconst CRM_SUITE = {\n feature: 'crm',\n message:\n 'The CRM — contacts, leads, companies, deals, tasks, pipelines, ' +\n 'activities and email templates — is not included in this organization’s plan',\n} as const\n\n/**\n * The CRM's resources on the customer REST API, each handler loaded with the\n * first request for it. In the order the API root lists them: the people\n * first, then the records a team keeps about them, then a site's leads\n * (`/v1/leads?siteId=`, a top-level path with the site as a parameter), then\n * the letters sent from a record (AGL-2658).\n */\nconst CRM_API_V1_RESOURCES: ReadonlyArray<readonly [string, ApiV1ResourceHandler]> = [\n ['contacts', async (...args) => (await import('./server/api-v1/contacts')).handleContacts(...args)],\n ['companies', async (...args) => (await import('./server/api-v1/crm-companies')).handleCompanies(...args)],\n ['pipelines', async (...args) => (await import('./server/api-v1/crm-pipelines')).handlePipelines(...args)],\n ['deals', async (...args) => (await import('./server/api-v1/crm-deals')).handleDeals(...args)],\n ['tasks', async (...args) => (await import('./server/api-v1/crm-tasks')).handleTasks(...args)],\n ['activities', async (...args) => (await import('./server/api-v1/crm-activities')).handleActivities(...args)],\n ['leads', async (...args) => (await import('./server/api-v1/crm-leads')).handleLeads(...args)],\n [\n 'email-templates',\n async (...args) => (await import('./server/api-v1/crm-email-templates')).handleEmailTemplates(...args),\n ],\n]\n\n/**\n * The CRM's CONSOLE-ONLY server declarations, named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot.\n *\n * It serves the CRM's resources of `/v1` (AGL-2606): the console's router\n * owns the pipeline in front of them — the key, the plan's API access, the\n * quota, the rate limit, the error envelope — and hands every request under\n * `/v1/contacts`, `/v1/deals` and the rest to the CRM once the key is\n * authenticated and the organization's plan carries the CRM. Each resource\n * brings its description for the API's OpenAPI document, and the CRM's\n * records band and collection sizes join `GET /v1/usage`. The API is the\n * console's alone, so the tenant runtime does not register them.\n *\n * Light at boot: each resource's module is imported with its first request,\n * the descriptions when the document is first built, and the usage reader\n * with the first usage call.\n * Registering again replaces this plugin's own entries, so a second call (a\n * hot reload, a spec) is harmless.\n */\nexport function registerCrmConsoleServerDeclarations(): void {\n for (const [resource, handle] of CRM_API_V1_RESOURCES) {\n registerApiV1Resource(\n resource,\n {\n handle,\n entitlement: CRM_SUITE,\n describe: async () =>\n (await import('./server/api-v1/openapi')).CRM_API_V1_DESCRIPTIONS[resource],\n },\n { pluginId: BUNDLE_ID },\n )\n }\n registerApiV1UsageFigures(\n async (ctx) => (await import('./server/api-v1/usage')).crmUsageFigures(ctx),\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["registerApiV1Resource","registerApiV1UsageFigures","BUNDLE_ID","CRM_SUITE","feature","message","CRM_API_V1_RESOURCES","args","handleContacts","handleCompanies","handlePipelines","handleDeals","handleTasks","handleActivities","handleLeads","handleEmailTemplates","registerCrmConsoleServerDeclarations","resource","handle","entitlement","describe","CRM_API_V1_DESCRIPTIONS","pluginId","ctx","crmUsageFigures"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,yEAAyE;AACzE,0CAA0C;AAC1C,SAEEA,qBAAqB,EACrBC,yBAAyB,QACpB,mDAAkD;AACzD,SAASC,SAAS,QAAQ,+BAA2B;AAErD;;;;;CAKC,GACD,MAAMC,YAAY;IAChBC,SAAS;IACTC,SACE,oEACA;AACJ;AAEA;;;;;;CAMC,GACD,MAAMC,uBAA+E;IACnF;QAAC;QAAY,OAAO,GAAGC,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,8BAA0B,EAAGC,cAAc,IAAID;KAAM;IACnG;QAAC;QAAa,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,mCAA+B,EAAGE,eAAe,IAAIF;KAAM;IAC1G;QAAC;QAAa,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,mCAA+B,EAAGG,eAAe,IAAIH;KAAM;IAC1G;QAAC;QAAS,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,+BAA2B,EAAGI,WAAW,IAAIJ;KAAM;IAC9F;QAAC;QAAS,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,+BAA2B,EAAGK,WAAW,IAAIL;KAAM;IAC9F;QAAC;QAAc,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,oCAAgC,EAAGM,gBAAgB,IAAIN;KAAM;IAC7G;QAAC;QAAS,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,+BAA2B,EAAGO,WAAW,IAAIP;KAAM;IAC9F;QACE;QACA,OAAO,GAAGA,OAAS,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGQ,oBAAoB,IAAIR;KAClG;CACF;AAED;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,SAASS;IACd,KAAK,MAAM,CAACC,UAAUC,OAAO,IAAIZ,qBAAsB;QACrDN,sBACEiB,UACA;YACEC;YACAC,aAAahB;YACbiB,UAAU,UACR,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGC,uBAAuB,CAACJ,SAAS;QAC/E,GACA;YAAEK,UAAUpB;QAAU;IAE1B;IACAD,0BACE,OAAOsB,MAAQ,AAAC,CAAA,MAAM,MAAM,CAAC,2BAAuB,EAAGC,eAAe,CAACD,MACvE;QAAED,UAAUpB;IAAU;AAE1B"}
@@ -15,6 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import { type PluginContactCaptureWriter } from '@aglyn/aglyn/plugin-manager/plugin-contact-capture';
18
+ import { type PluginRecordIndex } from '@aglyn/aglyn/plugin-manager/plugin-record-index';
18
19
  /**
19
20
  * The CRM as the plugin that keeps people.
20
21
  *
@@ -25,6 +26,11 @@ import { type PluginContactCaptureWriter } from '@aglyn/aglyn/plugin-manager/plu
25
26
  * including ones that will never touch the CRM.
26
27
  */
27
28
  export declare const crmContactCaptureWriter: PluginContactCaptureWriter;
29
+ /**
30
+ * The `pipeline` record index, its reads loaded with the first one — see
31
+ * `server/pipeline-record-index.ts` for what it answers.
32
+ */
33
+ export declare const crmPipelineRecordIndex: PluginRecordIndex;
28
34
  /**
29
35
  * The plugin's SERVER declarations: what the server must know at boot, before
30
36
  * any door of the CRM has been called.
@@ -19,7 +19,9 @@ import { registerPluginConsentGroupParticipant } from "@aglyn/aglyn/plugin-manag
19
19
  import { registerPluginContactCaptureWriter } from "@aglyn/aglyn/plugin-manager/plugin-contact-capture";
20
20
  import { registerPluginLeadConversionListener } from "@aglyn/aglyn/plugin-manager/plugin-lead-conversion";
21
21
  import { registerPluginPersonMatcher } from "@aglyn/aglyn/plugin-manager/plugin-person-matches";
22
+ import { registerPluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
22
23
  import { registerPluginRecordEmailStateWriter } from "@aglyn/aglyn/plugin-manager/plugin-record-email-state";
24
+ import { registerPluginRecordWrittenListener } from "@aglyn/aglyn/plugin-manager/plugin-record-written";
23
25
  import { BUNDLE_ID } from "./constants/bundle-common.js";
24
26
  import { summarizeConsentGroupChange } from "./model/consent-group-summary.js";
25
27
  /**
@@ -36,6 +38,19 @@ import { summarizeConsentGroupChange } from "./model/consent-group-summary.js";
36
38
  return await captureContactForCrm(request);
37
39
  }
38
40
  };
41
+ /**
42
+ * The `pipeline` record index, its reads loaded with the first one — see
43
+ * `server/pipeline-record-index.ts` for what it answers.
44
+ */ export const crmPipelineRecordIndex = {
45
+ async list (request) {
46
+ const { pipelineRecordIndex } = await import("./server/pipeline-record-index.js");
47
+ return pipelineRecordIndex.list(request);
48
+ },
49
+ async get (request) {
50
+ const { pipelineRecordIndex } = await import("./server/pipeline-record-index.js");
51
+ return pipelineRecordIndex.get(request);
52
+ }
53
+ };
39
54
  /**
40
55
  * The plugin's SERVER declarations: what the server must know at boot, before
41
56
  * any door of the CRM has been called.
@@ -105,6 +120,24 @@ import { summarizeConsentGroupChange } from "./model/consent-group-summary.js";
105
120
  }, {
106
121
  pluginId: BUNDLE_ID
107
122
  });
123
+ // Sharing rules (AGL-3336), re-evaluated after every server write of a
124
+ // lead, a contact, a company or a deal: the core tells its record-written
125
+ // listeners from the list-field restamp every such writer ends with, in
126
+ // both apps — a form's capture on the tenant, an edit in the console.
127
+ // Deferred like the rest: an org with no rules pays one cached read.
128
+ registerPluginRecordWrittenListener(async (event)=>{
129
+ const { crmSharingRecordWritten } = await import("./server/crm-sharing.js");
130
+ await crmSharingRecordWritten(event);
131
+ }, {
132
+ pluginId: BUNDLE_ID,
133
+ key: 'sharing'
134
+ });
135
+ // The organization's pipelines and their stages, as another plugin reads
136
+ // them (AGL-3080): an automation the AI drafts names the stage a deal
137
+ // moves to. Deferred like the rest: the reads load with the first one.
138
+ registerPluginRecordIndex('pipeline', crmPipelineRecordIndex, {
139
+ pluginId: BUNDLE_ID
140
+ });
108
141
  registerPluginConsentGroupParticipant({
109
142
  async preview (request) {
110
143
  const { consentGroupParticipant } = await import("./server/consent-group-participant.js");
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/declarations.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The contract from its own module, not the plugin-manager barrel: boot needs\n// the registry and nothing else, and the barrel reaches the client contexts.\nimport { registerPluginConsentGroupParticipant } from '@aglyn/aglyn/plugin-manager/plugin-consent-group-change'\nimport {\n registerPluginContactCaptureWriter,\n type PluginContactCaptureWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-contact-capture'\nimport { registerPluginLeadConversionListener } from '@aglyn/aglyn/plugin-manager/plugin-lead-conversion'\nimport { registerPluginPersonMatcher } from '@aglyn/aglyn/plugin-manager/plugin-person-matches'\nimport { registerPluginRecordEmailStateWriter } from '@aglyn/aglyn/plugin-manager/plugin-record-email-state'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { summarizeConsentGroupChange } from './model/consent-group-summary'\n\n/**\n * The CRM as the plugin that keeps people.\n *\n * Light by construction, the way the workflows listener is: the capture\n * itself is imported when the first one arrives, not when the process\n * starts, so a process that never captures anybody pays for this object and\n * nothing else. That matters because this registers in EVERY server process,\n * including ones that will never touch the CRM.\n */\nexport const crmContactCaptureWriter: PluginContactCaptureWriter = {\n async capture(request) {\n const { captureContactForCrm } = await import('./server/capture-contact')\n return await captureContactForCrm(request)\n },\n}\n\n/**\n * The plugin's SERVER declarations: what the server must know at boot, before\n * any door of the CRM has been called.\n *\n * ## Why a declaration and not a `tenantApi` registration\n *\n * The doors that meet a person mostly never load this plugin. A form\n * submission is a core route; an order and a booking are captured inside\n * STRIPE WEBHOOKS. None of them has a reason to load the CRM, and a registry\n * filled by a surface that did not load answers `null` — which\n * `capturePluginContact` defines as \"this workspace has no record system\", a\n * sentence that would be false and silent. Orders would stop creating\n * contacts with nothing red anywhere, which is the AGL-3025 shape.\n *\n * Running from both apps' generated server-declarations manifest at boot is\n * what makes the answer true in every process. It is also called from the\n * plugin's own API register functions, so a process whose boot did not run it\n * still registers the writer the first time a CRM door loads. Registering\n * twice replaces in place.\n *\n * ⚠️ The registry holds ONE writer: a workspace keeps one set of people. A\n * second plugin's is refused naming both, and the incumbent keeps serving.\n */\nexport function registerCrmServerDeclarations(): void {\n registerPluginContactCaptureWriter(crmContactCaptureWriter, {\n pluginId: BUNDLE_ID,\n })\n // The CRM's own share of a lead conversion (AGL-3254): the lead's\n // campaigns go onto the contact's facet. Through the seam every door\n // that converts a lead reaches, and deferred like the capture: the\n // module that writes is loaded when the first conversion arrives, and\n // it brings the Admin SDK with it — this file defers the plugin's OWN\n // module only. A library imported statically across the plugin cannot\n // also be lazy-loaded here: `enforce-module-boundaries` refuses every\n // static import of a library the project lazy-loads anywhere.\n registerPluginLeadConversionListener(\n async (request) => {\n const { carryLeadCampaignsOnConversion } =\n await import('./server/lead-campaign-carry')\n return carryLeadCampaignsOnConversion(request)\n },\n { pluginId: BUNDLE_ID },\n )\n // \"Did this workspace already know this person?\" (AGL-3289), asked by the\n // staff console's acquisition card. Deferred like the rest: the reads load\n // when a staff member first opens a card, not when the process starts.\n registerPluginPersonMatcher(\n async (request) => {\n const { matchCrmPeople } = await import('./server/person-matches')\n return matchCrmPeople(request)\n },\n { pluginId: BUNDLE_ID },\n )\n // The CRM's share of a consent group change (AGL-3320): a contact's\n // per-site refusal carried across a separation, and the records keyed by a\n // group re-homed after the flip. Registered whether or not a workspace has\n // the CRM switched on, because the contacts — and the refusals on them —\n // outlive the switch. Deferred like the rest; the summary is plain words\n // and answers synchronously.\n // The record email-state writer (AGL-3245), here as well as in the console\n // API so the tenant's doors reach it too: a form submission's capture is\n // checked for deliverability after its response (AGL-3328), and the \"Would\n // bounce\" that check finds is written through this seam. Deferred like the\n // rest; the console's own registration replaces this one with the same\n // writer, loaded eagerly.\n registerPluginRecordEmailStateWriter(\n {\n async stamp(request) {\n const { createCrmRecordEmailStateWriter, defaultCrmRecordEmailStateDeps } =\n await import('./server/record-email-state')\n return createCrmRecordEmailStateWriter(defaultCrmRecordEmailStateDeps()).stamp(request)\n },\n },\n { pluginId: BUNDLE_ID },\n )\n registerPluginConsentGroupParticipant(\n {\n async preview(request) {\n const { consentGroupParticipant } = await import('./server/consent-group-participant')\n return consentGroupParticipant.preview(request)\n },\n async run(request) {\n const { consentGroupParticipant } = await import('./server/consent-group-participant')\n return consentGroupParticipant.run(request)\n },\n summarize: summarizeConsentGroupChange,\n },\n { pluginId: BUNDLE_ID },\n )\n}\n\n"],"names":["registerPluginConsentGroupParticipant","registerPluginContactCaptureWriter","registerPluginLeadConversionListener","registerPluginPersonMatcher","registerPluginRecordEmailStateWriter","BUNDLE_ID","summarizeConsentGroupChange","crmContactCaptureWriter","capture","request","captureContactForCrm","registerCrmServerDeclarations","pluginId","carryLeadCampaignsOnConversion","matchCrmPeople","stamp","createCrmRecordEmailStateWriter","defaultCrmRecordEmailStateDeps","preview","consentGroupParticipant","run","summarize"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,6EAA6E;AAC7E,SAASA,qCAAqC,QAAQ,0DAAyD;AAC/G,SACEC,kCAAkC,QAE7B,qDAAoD;AAC3D,SAASC,oCAAoC,QAAQ,qDAAoD;AACzG,SAASC,2BAA2B,QAAQ,oDAAmD;AAC/F,SAASC,oCAAoC,QAAQ,wDAAuD;AAC5G,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,2BAA2B,QAAQ,mCAA+B;AAE3E;;;;;;;;CAQC,GACD,OAAO,MAAMC,0BAAsD;IACjE,MAAMC,SAAQC,OAAO;QACnB,MAAM,EAAEC,oBAAoB,EAAE,GAAG,MAAM,MAAM,CAAC;QAC9C,OAAO,MAAMA,qBAAqBD;IACpC;AACF,EAAC;AAED;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,SAASE;IACdV,mCAAmCM,yBAAyB;QAC1DK,UAAUP;IACZ;IACA,kEAAkE;IAClE,qEAAqE;IACrE,mEAAmE;IACnE,sEAAsE;IACtE,sEAAsE;IACtE,sEAAsE;IACtE,sEAAsE;IACtE,8DAA8D;IAC9DH,qCACE,OAAOO;QACL,MAAM,EAAEI,8BAA8B,EAAE,GACtC,MAAM,MAAM,CAAC;QACf,OAAOA,+BAA+BJ;IACxC,GACA;QAAEG,UAAUP;IAAU;IAExB,0EAA0E;IAC1E,2EAA2E;IAC3E,uEAAuE;IACvEF,4BACE,OAAOM;QACL,MAAM,EAAEK,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC;QACxC,OAAOA,eAAeL;IACxB,GACA;QAAEG,UAAUP;IAAU;IAExB,oEAAoE;IACpE,2EAA2E;IAC3E,2EAA2E;IAC3E,yEAAyE;IACzE,yEAAyE;IACzE,6BAA6B;IAC7B,2EAA2E;IAC3E,yEAAyE;IACzE,2EAA2E;IAC3E,2EAA2E;IAC3E,uEAAuE;IACvE,0BAA0B;IAC1BD,qCACE;QACE,MAAMW,OAAMN,OAAO;YACjB,MAAM,EAAEO,+BAA+B,EAAEC,8BAA8B,EAAE,GACvE,MAAM,MAAM,CAAC;YACf,OAAOD,gCAAgCC,kCAAkCF,KAAK,CAACN;QACjF;IACF,GACA;QAAEG,UAAUP;IAAU;IAExBL,sCACE;QACE,MAAMkB,SAAQT,OAAO;YACnB,MAAM,EAAEU,uBAAuB,EAAE,GAAG,MAAM,MAAM,CAAC;YACjD,OAAOA,wBAAwBD,OAAO,CAACT;QACzC;QACA,MAAMW,KAAIX,OAAO;YACf,MAAM,EAAEU,uBAAuB,EAAE,GAAG,MAAM,MAAM,CAAC;YACjD,OAAOA,wBAAwBC,GAAG,CAACX;QACrC;QACAY,WAAWf;IACb,GACA;QAAEM,UAAUP;IAAU;AAE1B"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/crm/src/lib/declarations.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The contract from its own module, not the plugin-manager barrel: boot needs\n// the registry and nothing else, and the barrel reaches the client contexts.\nimport { registerPluginConsentGroupParticipant } from '@aglyn/aglyn/plugin-manager/plugin-consent-group-change'\nimport {\n registerPluginContactCaptureWriter,\n type PluginContactCaptureWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-contact-capture'\nimport { registerPluginLeadConversionListener } from '@aglyn/aglyn/plugin-manager/plugin-lead-conversion'\nimport { registerPluginPersonMatcher } from '@aglyn/aglyn/plugin-manager/plugin-person-matches'\nimport {\n registerPluginRecordIndex,\n type PluginRecordIndex,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { registerPluginRecordEmailStateWriter } from '@aglyn/aglyn/plugin-manager/plugin-record-email-state'\nimport { registerPluginRecordWrittenListener } from '@aglyn/aglyn/plugin-manager/plugin-record-written'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { summarizeConsentGroupChange } from './model/consent-group-summary'\n\n/**\n * The CRM as the plugin that keeps people.\n *\n * Light by construction, the way the workflows listener is: the capture\n * itself is imported when the first one arrives, not when the process\n * starts, so a process that never captures anybody pays for this object and\n * nothing else. That matters because this registers in EVERY server process,\n * including ones that will never touch the CRM.\n */\nexport const crmContactCaptureWriter: PluginContactCaptureWriter = {\n async capture(request) {\n const { captureContactForCrm } = await import('./server/capture-contact')\n return await captureContactForCrm(request)\n },\n}\n\n/**\n * The `pipeline` record index, its reads loaded with the first one — see\n * `server/pipeline-record-index.ts` for what it answers.\n */\nexport const crmPipelineRecordIndex: PluginRecordIndex = {\n async list(request) {\n const { pipelineRecordIndex } = await import('./server/pipeline-record-index')\n return pipelineRecordIndex.list(request)\n },\n async get(request) {\n const { pipelineRecordIndex } = await import('./server/pipeline-record-index')\n return pipelineRecordIndex.get(request)\n },\n}\n\n/**\n * The plugin's SERVER declarations: what the server must know at boot, before\n * any door of the CRM has been called.\n *\n * ## Why a declaration and not a `tenantApi` registration\n *\n * The doors that meet a person mostly never load this plugin. A form\n * submission is a core route; an order and a booking are captured inside\n * STRIPE WEBHOOKS. None of them has a reason to load the CRM, and a registry\n * filled by a surface that did not load answers `null` — which\n * `capturePluginContact` defines as \"this workspace has no record system\", a\n * sentence that would be false and silent. Orders would stop creating\n * contacts with nothing red anywhere, which is the AGL-3025 shape.\n *\n * Running from both apps' generated server-declarations manifest at boot is\n * what makes the answer true in every process. It is also called from the\n * plugin's own API register functions, so a process whose boot did not run it\n * still registers the writer the first time a CRM door loads. Registering\n * twice replaces in place.\n *\n * ⚠️ The registry holds ONE writer: a workspace keeps one set of people. A\n * second plugin's is refused naming both, and the incumbent keeps serving.\n */\nexport function registerCrmServerDeclarations(): void {\n registerPluginContactCaptureWriter(crmContactCaptureWriter, {\n pluginId: BUNDLE_ID,\n })\n // The CRM's own share of a lead conversion (AGL-3254): the lead's\n // campaigns go onto the contact's facet. Through the seam every door\n // that converts a lead reaches, and deferred like the capture: the\n // module that writes is loaded when the first conversion arrives, and\n // it brings the Admin SDK with it — this file defers the plugin's OWN\n // module only. A library imported statically across the plugin cannot\n // also be lazy-loaded here: `enforce-module-boundaries` refuses every\n // static import of a library the project lazy-loads anywhere.\n registerPluginLeadConversionListener(\n async (request) => {\n const { carryLeadCampaignsOnConversion } =\n await import('./server/lead-campaign-carry')\n return carryLeadCampaignsOnConversion(request)\n },\n { pluginId: BUNDLE_ID },\n )\n // \"Did this workspace already know this person?\" (AGL-3289), asked by the\n // staff console's acquisition card. Deferred like the rest: the reads load\n // when a staff member first opens a card, not when the process starts.\n registerPluginPersonMatcher(\n async (request) => {\n const { matchCrmPeople } = await import('./server/person-matches')\n return matchCrmPeople(request)\n },\n { pluginId: BUNDLE_ID },\n )\n // The CRM's share of a consent group change (AGL-3320): a contact's\n // per-site refusal carried across a separation, and the records keyed by a\n // group re-homed after the flip. Registered whether or not a workspace has\n // the CRM switched on, because the contacts — and the refusals on them —\n // outlive the switch. Deferred like the rest; the summary is plain words\n // and answers synchronously.\n // The record email-state writer (AGL-3245), here as well as in the console\n // API so the tenant's doors reach it too: a form submission's capture is\n // checked for deliverability after its response (AGL-3328), and the \"Would\n // bounce\" that check finds is written through this seam. Deferred like the\n // rest; the console's own registration replaces this one with the same\n // writer, loaded eagerly.\n registerPluginRecordEmailStateWriter(\n {\n async stamp(request) {\n const { createCrmRecordEmailStateWriter, defaultCrmRecordEmailStateDeps } =\n await import('./server/record-email-state')\n return createCrmRecordEmailStateWriter(defaultCrmRecordEmailStateDeps()).stamp(request)\n },\n },\n { pluginId: BUNDLE_ID },\n )\n // Sharing rules (AGL-3336), re-evaluated after every server write of a\n // lead, a contact, a company or a deal: the core tells its record-written\n // listeners from the list-field restamp every such writer ends with, in\n // both apps — a form's capture on the tenant, an edit in the console.\n // Deferred like the rest: an org with no rules pays one cached read.\n registerPluginRecordWrittenListener(\n async (event) => {\n const { crmSharingRecordWritten } = await import('./server/crm-sharing')\n await crmSharingRecordWritten(event)\n },\n { pluginId: BUNDLE_ID, key: 'sharing' },\n )\n // The organization's pipelines and their stages, as another plugin reads\n // them (AGL-3080): an automation the AI drafts names the stage a deal\n // moves to. Deferred like the rest: the reads load with the first one.\n registerPluginRecordIndex('pipeline', crmPipelineRecordIndex, { pluginId: BUNDLE_ID })\n registerPluginConsentGroupParticipant(\n {\n async preview(request) {\n const { consentGroupParticipant } = await import('./server/consent-group-participant')\n return consentGroupParticipant.preview(request)\n },\n async run(request) {\n const { consentGroupParticipant } = await import('./server/consent-group-participant')\n return consentGroupParticipant.run(request)\n },\n summarize: summarizeConsentGroupChange,\n },\n { pluginId: BUNDLE_ID },\n )\n}\n\n"],"names":["registerPluginConsentGroupParticipant","registerPluginContactCaptureWriter","registerPluginLeadConversionListener","registerPluginPersonMatcher","registerPluginRecordIndex","registerPluginRecordEmailStateWriter","registerPluginRecordWrittenListener","BUNDLE_ID","summarizeConsentGroupChange","crmContactCaptureWriter","capture","request","captureContactForCrm","crmPipelineRecordIndex","list","pipelineRecordIndex","get","registerCrmServerDeclarations","pluginId","carryLeadCampaignsOnConversion","matchCrmPeople","stamp","createCrmRecordEmailStateWriter","defaultCrmRecordEmailStateDeps","event","crmSharingRecordWritten","key","preview","consentGroupParticipant","run","summarize"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,6EAA6E;AAC7E,SAASA,qCAAqC,QAAQ,0DAAyD;AAC/G,SACEC,kCAAkC,QAE7B,qDAAoD;AAC3D,SAASC,oCAAoC,QAAQ,qDAAoD;AACzG,SAASC,2BAA2B,QAAQ,oDAAmD;AAC/F,SACEC,yBAAyB,QAEpB,kDAAiD;AACxD,SAASC,oCAAoC,QAAQ,wDAAuD;AAC5G,SAASC,mCAAmC,QAAQ,oDAAmD;AACvG,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,2BAA2B,QAAQ,mCAA+B;AAE3E;;;;;;;;CAQC,GACD,OAAO,MAAMC,0BAAsD;IACjE,MAAMC,SAAQC,OAAO;QACnB,MAAM,EAAEC,oBAAoB,EAAE,GAAG,MAAM,MAAM,CAAC;QAC9C,OAAO,MAAMA,qBAAqBD;IACpC;AACF,EAAC;AAED;;;CAGC,GACD,OAAO,MAAME,yBAA4C;IACvD,MAAMC,MAAKH,OAAO;QAChB,MAAM,EAAEI,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC;QAC7C,OAAOA,oBAAoBD,IAAI,CAACH;IAClC;IACA,MAAMK,KAAIL,OAAO;QACf,MAAM,EAAEI,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC;QAC7C,OAAOA,oBAAoBC,GAAG,CAACL;IACjC;AACF,EAAC;AAED;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,SAASM;IACdhB,mCAAmCQ,yBAAyB;QAC1DS,UAAUX;IACZ;IACA,kEAAkE;IAClE,qEAAqE;IACrE,mEAAmE;IACnE,sEAAsE;IACtE,sEAAsE;IACtE,sEAAsE;IACtE,sEAAsE;IACtE,8DAA8D;IAC9DL,qCACE,OAAOS;QACL,MAAM,EAAEQ,8BAA8B,EAAE,GACtC,MAAM,MAAM,CAAC;QACf,OAAOA,+BAA+BR;IACxC,GACA;QAAEO,UAAUX;IAAU;IAExB,0EAA0E;IAC1E,2EAA2E;IAC3E,uEAAuE;IACvEJ,4BACE,OAAOQ;QACL,MAAM,EAAES,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC;QACxC,OAAOA,eAAeT;IACxB,GACA;QAAEO,UAAUX;IAAU;IAExB,oEAAoE;IACpE,2EAA2E;IAC3E,2EAA2E;IAC3E,yEAAyE;IACzE,yEAAyE;IACzE,6BAA6B;IAC7B,2EAA2E;IAC3E,yEAAyE;IACzE,2EAA2E;IAC3E,2EAA2E;IAC3E,uEAAuE;IACvE,0BAA0B;IAC1BF,qCACE;QACE,MAAMgB,OAAMV,OAAO;YACjB,MAAM,EAAEW,+BAA+B,EAAEC,8BAA8B,EAAE,GACvE,MAAM,MAAM,CAAC;YACf,OAAOD,gCAAgCC,kCAAkCF,KAAK,CAACV;QACjF;IACF,GACA;QAAEO,UAAUX;IAAU;IAExB,uEAAuE;IACvE,0EAA0E;IAC1E,wEAAwE;IACxE,sEAAsE;IACtE,qEAAqE;IACrED,oCACE,OAAOkB;QACL,MAAM,EAAEC,uBAAuB,EAAE,GAAG,MAAM,MAAM,CAAC;QACjD,MAAMA,wBAAwBD;IAChC,GACA;QAAEN,UAAUX;QAAWmB,KAAK;IAAU;IAExC,yEAAyE;IACzE,sEAAsE;IACtE,uEAAuE;IACvEtB,0BAA0B,YAAYS,wBAAwB;QAAEK,UAAUX;IAAU;IACpFP,sCACE;QACE,MAAM2B,SAAQhB,OAAO;YACnB,MAAM,EAAEiB,uBAAuB,EAAE,GAAG,MAAM,MAAM,CAAC;YACjD,OAAOA,wBAAwBD,OAAO,CAAChB;QACzC;QACA,MAAMkB,KAAIlB,OAAO;YACf,MAAM,EAAEiB,uBAAuB,EAAE,GAAG,MAAM,MAAM,CAAC;YACjD,OAAOA,wBAAwBC,GAAG,CAAClB;QACrC;QACAmB,WAAWtB;IACb,GACA;QAAEU,UAAUX;IAAU;AAE1B"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { CrmBulkAnswer } from '../model/crm-bulk-writes';
18
+ /**
19
+ * THE CONSOLE'S DOOR TO `crm/contact-remove` (AGL-3338).
20
+ *
21
+ * The record page's Delete and the contacts bar's Remove let contacts go
22
+ * through here rather than writing the document: the route decides detach
23
+ * or delete against the stored consent map, keeps every refusal, and the
24
+ * rules refuse a client the write.
25
+ *
26
+ * A request refused WHOLE — no session, no permission — throws with the
27
+ * route's sentence. `removeMany` answers per contact in the bulk runners'
28
+ * shape; `removeOne` throws that contact's refusal and otherwise says
29
+ * whether the document was deleted or only this site's half of it.
30
+ */
31
+ export declare function useContactRemove(hostId: string | null): {
32
+ removeMany: (contactIds: readonly string[]) => Promise<CrmBulkAnswer[]>;
33
+ removeOne: (contactId: string) => Promise<"deleted" | "detached">;
34
+ };
35
+ export default useContactRemove;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { useCallback, useMemo } from "react";
19
+ import { useCrmApi } from "../components/use-crm-api.js";
20
+ /**
21
+ * THE CONSOLE'S DOOR TO `crm/contact-remove` (AGL-3338).
22
+ *
23
+ * The record page's Delete and the contacts bar's Remove let contacts go
24
+ * through here rather than writing the document: the route decides detach
25
+ * or delete against the stored consent map, keeps every refusal, and the
26
+ * rules refuse a client the write.
27
+ *
28
+ * A request refused WHOLE — no session, no permission — throws with the
29
+ * route's sentence. `removeMany` answers per contact in the bulk runners'
30
+ * shape; `removeOne` throws that contact's refusal and otherwise says
31
+ * whether the document was deleted or only this site's half of it.
32
+ */ export function useContactRemove(hostId) {
33
+ const callCrm = useCrmApi(hostId);
34
+ const removeOutcomes = useCallback(async (contactIds)=>{
35
+ const { response, payload } = await callCrm('contact-remove', {
36
+ contactIds: [
37
+ ...contactIds
38
+ ]
39
+ });
40
+ if (!response.ok) {
41
+ const error = payload['error'];
42
+ throw new Error(typeof error === 'string' && error ? error : `The contact could not be removed (${response.status}).`);
43
+ }
44
+ return Array.isArray(payload['results']) ? payload['results'] : [];
45
+ }, [
46
+ callCrm
47
+ ]);
48
+ const removeMany = useCallback(async (contactIds)=>(await removeOutcomes(contactIds)).map((outcome)=>_extends({
49
+ id: outcome.contactId,
50
+ ok: outcome.ok
51
+ }, 'error' in outcome ? {
52
+ error: outcome.error
53
+ } : {})), [
54
+ removeOutcomes
55
+ ]);
56
+ const removeOne = useCallback(async (contactId)=>{
57
+ const [outcome] = await removeOutcomes([
58
+ contactId
59
+ ]);
60
+ if (!(outcome == null ? void 0 : outcome.ok)) {
61
+ throw new Error(outcome && 'error' in outcome && outcome.error || 'The contact could not be removed.');
62
+ }
63
+ return outcome.removed;
64
+ }, [
65
+ removeOutcomes
66
+ ]);
67
+ return useMemo(()=>({
68
+ removeMany,
69
+ removeOne
70
+ }), [
71
+ removeMany,
72
+ removeOne
73
+ ]);
74
+ }
75
+ export default useContactRemove;
76
+
77
+ //# sourceMappingURL=use-contact-remove.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/hooks/use-contact-remove.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { useCallback, useMemo } from 'react'\nimport { useCrmApi } from '../components/use-crm-api'\nimport type { ContactRemoveOutcome } from '../model/contact-remove'\nimport type { CrmBulkAnswer } from '../model/crm-bulk-writes'\n\n/**\n * THE CONSOLE'S DOOR TO `crm/contact-remove` (AGL-3338).\n *\n * The record page's Delete and the contacts bar's Remove let contacts go\n * through here rather than writing the document: the route decides detach\n * or delete against the stored consent map, keeps every refusal, and the\n * rules refuse a client the write.\n *\n * A request refused WHOLE — no session, no permission — throws with the\n * route's sentence. `removeMany` answers per contact in the bulk runners'\n * shape; `removeOne` throws that contact's refusal and otherwise says\n * whether the document was deleted or only this site's half of it.\n */\nexport function useContactRemove(hostId: string | null) {\n const callCrm = useCrmApi(hostId)\n\n const removeOutcomes = useCallback(\n async (contactIds: readonly string[]): Promise<ContactRemoveOutcome[]> => {\n const { response, payload } = await callCrm('contact-remove', {\n contactIds: [...contactIds],\n })\n if (!response.ok) {\n const error = payload['error']\n throw new Error(\n typeof error === 'string' && error\n ? error\n : `The contact could not be removed (${response.status}).`,\n )\n }\n return Array.isArray(payload['results']) ? payload['results'] : []\n },\n [callCrm],\n )\n\n const removeMany = useCallback(\n async (contactIds: readonly string[]): Promise<CrmBulkAnswer[]> =>\n (await removeOutcomes(contactIds)).map((outcome) => ({\n id: outcome.contactId,\n ok: outcome.ok,\n ...('error' in outcome ? { error: outcome.error } : {}),\n })),\n [removeOutcomes],\n )\n\n const removeOne = useCallback(\n async (contactId: string): Promise<'deleted' | 'detached'> => {\n const [outcome] = await removeOutcomes([contactId])\n if (!outcome?.ok) {\n throw new Error(\n (outcome && 'error' in outcome && outcome.error) || 'The contact could not be removed.',\n )\n }\n return outcome.removed\n },\n [removeOutcomes],\n )\n\n return useMemo(() => ({ removeMany, removeOne }), [removeMany, removeOne])\n}\n\nexport default useContactRemove\n"],"names":["useCallback","useMemo","useCrmApi","useContactRemove","hostId","callCrm","removeOutcomes","contactIds","response","payload","ok","error","Error","status","Array","isArray","removeMany","map","outcome","id","contactId","removeOne","removed"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAEA,SAASA,WAAW,EAAEC,OAAO,QAAQ,QAAO;AAC5C,SAASC,SAAS,QAAQ,+BAA2B;AAIrD;;;;;;;;;;;;CAYC,GACD,OAAO,SAASC,iBAAiBC,MAAqB;IACpD,MAAMC,UAAUH,UAAUE;IAE1B,MAAME,iBAAiBN,YACrB,OAAOO;QACL,MAAM,EAAEC,QAAQ,EAAEC,OAAO,EAAE,GAAG,MAAMJ,QAAQ,kBAAkB;YAC5DE,YAAY;mBAAIA;aAAW;QAC7B;QACA,IAAI,CAACC,SAASE,EAAE,EAAE;YAChB,MAAMC,QAAQF,OAAO,CAAC,QAAQ;YAC9B,MAAM,IAAIG,MACR,OAAOD,UAAU,YAAYA,QACzBA,QACA,CAAC,kCAAkC,EAAEH,SAASK,MAAM,CAAC,EAAE,CAAC;QAEhE;QACA,OAAOC,MAAMC,OAAO,CAACN,OAAO,CAAC,UAAU,IAAIA,OAAO,CAAC,UAAU,GAAG,EAAE;IACpE,GACA;QAACJ;KAAQ;IAGX,MAAMW,aAAahB,YACjB,OAAOO,aACL,AAAC,CAAA,MAAMD,eAAeC,WAAU,EAAGU,GAAG,CAAC,CAACC,UAAa;gBACnDC,IAAID,QAAQE,SAAS;gBACrBV,IAAIQ,QAAQR,EAAE;eACV,WAAWQ,UAAU;gBAAEP,OAAOO,QAAQP,KAAK;YAAC,IAAI,CAAC,KAEzD;QAACL;KAAe;IAGlB,MAAMe,YAAYrB,YAChB,OAAOoB;QACL,MAAM,CAACF,QAAQ,GAAG,MAAMZ,eAAe;YAACc;SAAU;QAClD,IAAI,EAACF,2BAAAA,QAASR,EAAE,GAAE;YAChB,MAAM,IAAIE,MACR,AAACM,WAAW,WAAWA,WAAWA,QAAQP,KAAK,IAAK;QAExD;QACA,OAAOO,QAAQI,OAAO;IACxB,GACA;QAAChB;KAAe;IAGlB,OAAOL,QAAQ,IAAO,CAAA;YAAEe;YAAYK;QAAU,CAAA,GAAI;QAACL;QAAYK;KAAU;AAC3E;AAEA,eAAelB,iBAAgB"}
@@ -0,0 +1,30 @@
1
+ import { type CrmSharingObject, type CrmSharingSite } from '../model/crm-sharing';
2
+ import type { CrmOrgDoc } from './use-crm-scope';
3
+ export interface CrmSharingSites {
4
+ sites: CrmSharingSite[];
5
+ ready: boolean;
6
+ /** How a site reads: its name, or a neutral phrase for one this reader cannot name. */
7
+ siteName: (hostId: string) => string;
8
+ }
9
+ /**
10
+ * The org's sites, for a sharing surface (AGL-3336).
11
+ *
12
+ * At the organization level the hub already holds them (`useCrmOrgMount`).
13
+ * Under a site a member cannot list the org's other sites, so a MANAGER's
14
+ * surface asks `crm/sharing` for them — the one reader a share is offered
15
+ * to — and every other reader names them as "another site".
16
+ */
17
+ export declare function useCrmSharingSites(input: {
18
+ hostId: string | null;
19
+ orgId: string | null;
20
+ enabled: boolean;
21
+ }): CrmSharingSites;
22
+ /** The org's sharing rules, off the org document the shell already passed. */
23
+ export declare function useCrmSharingRules(org: CrmOrgDoc): import("../model/crm-sharing").CrmSharingRule[];
24
+ /**
25
+ * The follow-up a CLIENT-DIRECT write to a lead, a company or a deal owes
26
+ * the org's sharing rules (AGL-3336): a server write is followed by the
27
+ * core's record-written seam, a browser's by this. Fire-and-forget — the
28
+ * save has landed whether or not a rule then shares the record.
29
+ */
30
+ export declare function useCrmSharingFollowUp(hostId: string | null | undefined, orgId: string | null | undefined): (object: CrmSharingObject, ids: readonly string[]) => void;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { useUser } from "@aglyn/tenant-feature-instance";
18
+ import { useCallback, useEffect, useMemo, useState } from "react";
19
+ import { readCrmSharingRules } from "../model/crm-sharing.js";
20
+ import { callCrmSharing, followUpCrmSharing } from "../model/crm-sharing-api.js";
21
+ import { crmTaskCallScope } from "../model/task-routes.js";
22
+ import { useCrmOrgMount } from "./use-crm-org-mount.js";
23
+ /**
24
+ * The org's sites, for a sharing surface (AGL-3336).
25
+ *
26
+ * At the organization level the hub already holds them (`useCrmOrgMount`).
27
+ * Under a site a member cannot list the org's other sites, so a MANAGER's
28
+ * surface asks `crm/sharing` for them — the one reader a share is offered
29
+ * to — and every other reader names them as "another site".
30
+ */ export function useCrmSharingSites(input) {
31
+ const { hostId, orgId, enabled } = input;
32
+ const mount = useCrmOrgMount();
33
+ const { data: user } = useUser();
34
+ const [fetched, setFetched] = useState(null);
35
+ const scope = crmTaskCallScope(hostId, orgId);
36
+ const key = scope ? JSON.stringify(scope) : '';
37
+ const fromMount = mount && !hostId;
38
+ useEffect(()=>{
39
+ if (fromMount || !enabled || !scope || !user) return;
40
+ let cancelled = false;
41
+ callCrmSharing(user, scope, 'sites').then((answer)=>{
42
+ var _answer_sites;
43
+ if (!cancelled) setFetched({
44
+ key,
45
+ sites: (_answer_sites = answer.sites) != null ? _answer_sites : []
46
+ });
47
+ }).catch(()=>{
48
+ if (!cancelled) setFetched({
49
+ key,
50
+ sites: []
51
+ });
52
+ });
53
+ return ()=>{
54
+ cancelled = true;
55
+ };
56
+ // `scope` is keyed by `key`; the object itself is rebuilt every render.
57
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ }, [
59
+ fromMount,
60
+ enabled,
61
+ key,
62
+ user
63
+ ]);
64
+ const sites = useMemo(()=>fromMount ? mount.hosts.map((host)=>({
65
+ id: host.id,
66
+ name: host.name
67
+ })) : (fetched == null ? void 0 : fetched.key) === key ? fetched.sites : [], [
68
+ fromMount,
69
+ mount,
70
+ fetched,
71
+ key
72
+ ]);
73
+ const ready = fromMount ? mount.hostsReady : (fetched == null ? void 0 : fetched.key) === key || !enabled;
74
+ const siteName = useCallback((id)=>{
75
+ var _ref;
76
+ var _sites_find;
77
+ return (_ref = (_sites_find = sites.find((site)=>site.id === id)) == null ? void 0 : _sites_find.name) != null ? _ref : id === hostId ? 'This site' : 'another site';
78
+ }, [
79
+ sites,
80
+ hostId
81
+ ]);
82
+ return {
83
+ sites,
84
+ ready,
85
+ siteName
86
+ };
87
+ }
88
+ /** The org's sharing rules, off the org document the shell already passed. */ export function useCrmSharingRules(org) {
89
+ return useMemo(()=>readCrmSharingRules(org != null ? org : null), [
90
+ org
91
+ ]);
92
+ }
93
+ /**
94
+ * The follow-up a CLIENT-DIRECT write to a lead, a company or a deal owes
95
+ * the org's sharing rules (AGL-3336): a server write is followed by the
96
+ * core's record-written seam, a browser's by this. Fire-and-forget — the
97
+ * save has landed whether or not a rule then shares the record.
98
+ */ export function useCrmSharingFollowUp(hostId, orgId) {
99
+ const { data: user } = useUser();
100
+ return useCallback((object, ids)=>{
101
+ void followUpCrmSharing(user, crmTaskCallScope(hostId, orgId), object, ids);
102
+ }, [
103
+ user,
104
+ hostId,
105
+ orgId
106
+ ]);
107
+ }
108
+
109
+ //# sourceMappingURL=use-crm-sharing.js.map