@plotday/twister 0.57.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/README.md +53 -44
  2. package/bin/commands/create.js +9 -14
  3. package/bin/commands/create.js.map +1 -1
  4. package/bin/commands/deploy.js +2 -0
  5. package/bin/commands/deploy.js.map +1 -1
  6. package/bin/commands/generate.js +8 -5
  7. package/bin/commands/generate.js.map +1 -1
  8. package/bin/index.js +2 -2
  9. package/bin/index.js.map +1 -1
  10. package/bin/templates/AGENTS.template.md +110 -94
  11. package/bin/templates/README.template.md +36 -33
  12. package/cli/templates/AGENTS.template.md +110 -94
  13. package/cli/templates/README.template.md +36 -33
  14. package/dist/connector.d.ts +58 -17
  15. package/dist/connector.d.ts.map +1 -1
  16. package/dist/connector.js +51 -13
  17. package/dist/connector.js.map +1 -1
  18. package/dist/docs/assets/hierarchy.js +1 -1
  19. package/dist/docs/assets/navigation.js +1 -1
  20. package/dist/docs/assets/search.js +1 -1
  21. package/dist/docs/classes/index.Connector.html +90 -60
  22. package/dist/docs/classes/index.FileNotFoundError.html +2 -2
  23. package/dist/docs/classes/index.Files.html +4 -4
  24. package/dist/docs/classes/index.Imap.html +10 -10
  25. package/dist/docs/classes/index.Options.html +2 -2
  26. package/dist/docs/classes/index.Smtp.html +6 -6
  27. package/dist/docs/classes/tool.ITool.html +2 -2
  28. package/dist/docs/classes/tool.Tool.html +23 -23
  29. package/dist/docs/classes/tools_ai.AI.html +5 -5
  30. package/dist/docs/classes/tools_callbacks.Callbacks.html +8 -8
  31. package/dist/docs/classes/tools_integrations.Integrations.html +15 -15
  32. package/dist/docs/classes/tools_network.Network.html +9 -9
  33. package/dist/docs/classes/tools_plot.Plot.html +34 -33
  34. package/dist/docs/classes/tools_store.Store.html +8 -8
  35. package/dist/docs/classes/tools_tasks.Tasks.html +6 -6
  36. package/dist/docs/classes/tools_twists.Twists.html +12 -11
  37. package/dist/docs/classes/twist.Twist.html +28 -28
  38. package/dist/docs/documents/Building_Connectors.html +42 -28
  39. package/dist/docs/documents/Built-in_Tools.html +170 -67
  40. package/dist/docs/documents/CLI_Reference.html +68 -47
  41. package/dist/docs/documents/Core_Concepts.html +52 -81
  42. package/dist/docs/documents/Getting_Started.html +28 -31
  43. package/dist/docs/documents/MULTI_USER_AUTH.html +45 -0
  44. package/dist/docs/documents/Runtime_Environment.html +13 -12
  45. package/dist/docs/documents/SYNC_STRATEGIES.html +373 -0
  46. package/dist/docs/enums/plot.ActionType.html +9 -9
  47. package/dist/docs/enums/plot.ActorType.html +4 -4
  48. package/dist/docs/enums/plot.ConferencingProvider.html +6 -6
  49. package/dist/docs/enums/plot.ThemeColor.html +9 -9
  50. package/dist/docs/enums/tag.Tag.html +3 -3
  51. package/dist/docs/enums/tools_ai.AIModel.html +3 -3
  52. package/dist/docs/enums/tools_integrations.AuthProvider.html +13 -13
  53. package/dist/docs/enums/tools_plot.ContactAccess.html +2 -2
  54. package/dist/docs/enums/tools_plot.FocusAccess.html +3 -3
  55. package/dist/docs/enums/tools_plot.LinkAccess.html +3 -3
  56. package/dist/docs/enums/tools_plot.ThreadAccess.html +4 -4
  57. package/dist/docs/functions/index.Uuid.Generate.html +1 -1
  58. package/dist/docs/functions/utils_hash.quickHash.html +1 -1
  59. package/dist/docs/hierarchy.html +1 -1
  60. package/dist/docs/index.html +7 -8
  61. package/dist/docs/interfaces/tools_ai.AIRequest.html +13 -13
  62. package/dist/docs/interfaces/tools_ai.AIResponse.html +9 -9
  63. package/dist/docs/interfaces/tools_ai.FilePart.html +5 -5
  64. package/dist/docs/interfaces/tools_ai.ImagePart.html +4 -4
  65. package/dist/docs/interfaces/tools_ai.ReasoningPart.html +4 -4
  66. package/dist/docs/interfaces/tools_ai.RedactedReasoningPart.html +3 -3
  67. package/dist/docs/interfaces/tools_ai.TextPart.html +3 -3
  68. package/dist/docs/interfaces/tools_ai.ToolCallPart.html +5 -5
  69. package/dist/docs/interfaces/tools_ai.ToolExecutionOptions.html +4 -4
  70. package/dist/docs/interfaces/tools_ai.ToolResultPart.html +5 -5
  71. package/dist/docs/interfaces/tools_twists.TwistSource.html +3 -3
  72. package/dist/docs/interfaces/utils_types.ToolShed.html +5 -5
  73. package/dist/docs/media/AGENTS.md +101 -74
  74. package/dist/docs/modules.html +1 -1
  75. package/dist/docs/types/index.BooleanDef.html +2 -2
  76. package/dist/docs/types/index.CreateLinkDraft.html +9 -9
  77. package/dist/docs/types/index.ImapAddress.html +3 -3
  78. package/dist/docs/types/index.ImapConnectOptions.html +6 -6
  79. package/dist/docs/types/index.ImapFetchOptions.html +4 -4
  80. package/dist/docs/types/index.ImapFlagOperation.html +1 -1
  81. package/dist/docs/types/index.ImapMailbox.html +5 -5
  82. package/dist/docs/types/index.ImapMailboxStatus.html +7 -7
  83. package/dist/docs/types/index.ImapMessage.html +14 -14
  84. package/dist/docs/types/index.ImapSearchCriteria.html +9 -9
  85. package/dist/docs/types/index.ImapSession.html +1 -1
  86. package/dist/docs/types/index.NewSchedule.html +13 -13
  87. package/dist/docs/types/index.NewScheduleContact.html +2 -2
  88. package/dist/docs/types/index.NewScheduleOccurrence.html +1 -1
  89. package/dist/docs/types/index.NoteWriteBackResult.html +3 -3
  90. package/dist/docs/types/index.NumberDef.html +2 -2
  91. package/dist/docs/types/index.OptionDef.html +1 -1
  92. package/dist/docs/types/index.OptionalScopeGroup.html +6 -6
  93. package/dist/docs/types/index.OptionsSchema.html +1 -1
  94. package/dist/docs/types/index.ReactionCapabilities.html +1 -1
  95. package/dist/docs/types/index.ResolvedOptions.html +1 -1
  96. package/dist/docs/types/index.ResolvedRecipient.html +5 -5
  97. package/dist/docs/types/index.Schedule.html +12 -12
  98. package/dist/docs/types/index.ScheduleContact.html +2 -2
  99. package/dist/docs/types/index.ScheduleContactRole.html +1 -1
  100. package/dist/docs/types/index.ScheduleContactStatus.html +1 -1
  101. package/dist/docs/types/index.ScheduleOccurrence.html +6 -6
  102. package/dist/docs/types/index.ScheduleOccurrenceUpdate.html +1 -1
  103. package/dist/docs/types/index.ScopeConfig.html +3 -3
  104. package/dist/docs/types/index.SelectDef.html +2 -2
  105. package/dist/docs/types/index.Serializable.html +1 -1
  106. package/dist/docs/types/index.SmtpAddress.html +3 -3
  107. package/dist/docs/types/index.SmtpConnectOptions.html +7 -7
  108. package/dist/docs/types/index.SmtpMessage.html +12 -12
  109. package/dist/docs/types/index.SmtpSendResult.html +4 -4
  110. package/dist/docs/types/index.SmtpSession.html +1 -1
  111. package/dist/docs/types/index.TextDef.html +2 -2
  112. package/dist/docs/types/index.Uuid.html +1 -1
  113. package/dist/docs/types/plot.Action.html +1 -1
  114. package/dist/docs/types/plot.Actor.html +5 -5
  115. package/dist/docs/types/plot.ActorId.html +4 -4
  116. package/dist/docs/types/plot.Contact.html +4 -4
  117. package/dist/docs/types/plot.ContentType.html +1 -1
  118. package/dist/docs/types/plot.Focus.html +8 -8
  119. package/dist/docs/types/plot.FocusUpdate.html +1 -1
  120. package/dist/docs/types/plot.Link.html +17 -17
  121. package/dist/docs/types/plot.LinkUpdate.html +1 -1
  122. package/dist/docs/types/plot.NewActor.html +1 -1
  123. package/dist/docs/types/plot.NewContact.html +1 -1
  124. package/dist/docs/types/plot.NewFocus.html +1 -1
  125. package/dist/docs/types/plot.NewLink.html +5 -2
  126. package/dist/docs/types/plot.NewLinkWithNotes.html +1 -1
  127. package/dist/docs/types/plot.NewNote.html +1 -1
  128. package/dist/docs/types/plot.NewReactions.html +1 -1
  129. package/dist/docs/types/plot.NewTags.html +1 -1
  130. package/dist/docs/types/plot.NewThread.html +1 -1
  131. package/dist/docs/types/plot.NewThreadWithNotes.html +1 -1
  132. package/dist/docs/types/plot.Note.html +1 -1
  133. package/dist/docs/types/plot.NoteUpdate.html +1 -1
  134. package/dist/docs/types/plot.PlanOperation.html +1 -1
  135. package/dist/docs/types/plot.Reaction.html +3 -3
  136. package/dist/docs/types/plot.Reactions.html +1 -1
  137. package/dist/docs/types/plot.Tags.html +1 -1
  138. package/dist/docs/types/plot.Thread.html +1 -1
  139. package/dist/docs/types/plot.ThreadAccessLevel.html +1 -1
  140. package/dist/docs/types/plot.ThreadCommon.html +6 -6
  141. package/dist/docs/types/plot.ThreadFilter.html +2 -2
  142. package/dist/docs/types/plot.ThreadMeta.html +1 -1
  143. package/dist/docs/types/plot.ThreadType.html +1 -1
  144. package/dist/docs/types/plot.ThreadUpdate.html +1 -1
  145. package/dist/docs/types/plot.ThreadWithNotes.html +1 -1
  146. package/dist/docs/types/tools_ai.AIAssistantMessage.html +2 -2
  147. package/dist/docs/types/tools_ai.AICapabilities.html +4 -4
  148. package/dist/docs/types/tools_ai.AIMessage.html +1 -1
  149. package/dist/docs/types/tools_ai.AIOptions.html +2 -2
  150. package/dist/docs/types/tools_ai.AISource.html +1 -1
  151. package/dist/docs/types/tools_ai.AISystemMessage.html +2 -2
  152. package/dist/docs/types/tools_ai.AITool.html +1 -1
  153. package/dist/docs/types/tools_ai.AIToolMessage.html +2 -2
  154. package/dist/docs/types/tools_ai.AIToolSet.html +1 -1
  155. package/dist/docs/types/tools_ai.AIUsage.html +5 -5
  156. package/dist/docs/types/tools_ai.AIUserMessage.html +2 -2
  157. package/dist/docs/types/tools_ai.DataContent.html +1 -1
  158. package/dist/docs/types/tools_ai.ModelPreferences.html +5 -5
  159. package/dist/docs/types/tools_callbacks.Callback.html +2 -2
  160. package/dist/docs/types/tools_integrations.ArchiveLinkFilter.html +5 -5
  161. package/dist/docs/types/tools_integrations.ArchiveNotesFilter.html +2 -2
  162. package/dist/docs/types/tools_integrations.AuthToken.html +6 -5
  163. package/dist/docs/types/tools_integrations.Authorization.html +4 -4
  164. package/dist/docs/types/tools_integrations.Channel.html +6 -6
  165. package/dist/docs/types/tools_integrations.ComposeConfig.html +4 -4
  166. package/dist/docs/types/tools_integrations.ContactRoleConfig.html +5 -5
  167. package/dist/docs/types/tools_integrations.LinkTypeConfig.html +21 -21
  168. package/dist/docs/types/tools_integrations.NewCustomEmoji.html +8 -8
  169. package/dist/docs/types/tools_integrations.StatusIcon.html +1 -1
  170. package/dist/docs/types/tools_integrations.SyncContext.html +4 -4
  171. package/dist/docs/types/tools_network.WebhookRequest.html +6 -6
  172. package/dist/docs/types/tools_plot.LinkFilter.html +5 -5
  173. package/dist/docs/types/tools_plot.LinkSearchResult.html +1 -1
  174. package/dist/docs/types/tools_plot.NoteIntentHandler.html +4 -4
  175. package/dist/docs/types/tools_plot.NoteSearchResult.html +1 -1
  176. package/dist/docs/types/tools_plot.SearchOptions.html +4 -4
  177. package/dist/docs/types/tools_plot.SearchResult.html +1 -1
  178. package/dist/docs/types/tools_twists.Log.html +2 -2
  179. package/dist/docs/types/tools_twists.TwistPermissions.html +1 -1
  180. package/dist/docs/types/utils_types.BuiltInTools.html +2 -2
  181. package/dist/docs/types/utils_types.ExtractBuildReturn.html +1 -1
  182. package/dist/docs/types/utils_types.InferOptions.html +1 -1
  183. package/dist/docs/types/utils_types.InferTools.html +1 -1
  184. package/dist/docs/types/utils_types.JSONValue.html +1 -1
  185. package/dist/docs/types/utils_types.PromiseValues.html +1 -1
  186. package/dist/docs/types/utils_types.ToolBuilder.html +1 -1
  187. package/dist/docs/variables/tools_plot.SEARCH_DEFAULT_LIMIT.html +1 -1
  188. package/dist/docs/variables/tools_plot.SEARCH_MAX_LIMIT.html +1 -1
  189. package/dist/facets.d.ts +30 -0
  190. package/dist/facets.d.ts.map +1 -0
  191. package/dist/facets.js +16 -0
  192. package/dist/facets.js.map +1 -0
  193. package/dist/llm-docs/connector.d.ts +1 -1
  194. package/dist/llm-docs/connector.d.ts.map +1 -1
  195. package/dist/llm-docs/connector.js +1 -1
  196. package/dist/llm-docs/connector.js.map +1 -1
  197. package/dist/llm-docs/facets.d.ts +9 -0
  198. package/dist/llm-docs/facets.d.ts.map +1 -0
  199. package/dist/llm-docs/facets.js +8 -0
  200. package/dist/llm-docs/facets.js.map +1 -0
  201. package/dist/llm-docs/index.d.ts.map +1 -1
  202. package/dist/llm-docs/index.js +2 -0
  203. package/dist/llm-docs/index.js.map +1 -1
  204. package/dist/llm-docs/plot.d.ts +1 -1
  205. package/dist/llm-docs/plot.d.ts.map +1 -1
  206. package/dist/llm-docs/plot.js +1 -1
  207. package/dist/llm-docs/plot.js.map +1 -1
  208. package/dist/llm-docs/tool.d.ts +1 -1
  209. package/dist/llm-docs/tool.d.ts.map +1 -1
  210. package/dist/llm-docs/tool.js +1 -1
  211. package/dist/llm-docs/tool.js.map +1 -1
  212. package/dist/llm-docs/tools/ai.d.ts +1 -1
  213. package/dist/llm-docs/tools/ai.d.ts.map +1 -1
  214. package/dist/llm-docs/tools/ai.js +1 -1
  215. package/dist/llm-docs/tools/ai.js.map +1 -1
  216. package/dist/llm-docs/tools/callbacks.d.ts +1 -1
  217. package/dist/llm-docs/tools/callbacks.d.ts.map +1 -1
  218. package/dist/llm-docs/tools/callbacks.js +1 -1
  219. package/dist/llm-docs/tools/callbacks.js.map +1 -1
  220. package/dist/llm-docs/tools/files.d.ts +1 -1
  221. package/dist/llm-docs/tools/files.d.ts.map +1 -1
  222. package/dist/llm-docs/tools/files.js +1 -1
  223. package/dist/llm-docs/tools/files.js.map +1 -1
  224. package/dist/llm-docs/tools/imap.d.ts +1 -1
  225. package/dist/llm-docs/tools/imap.d.ts.map +1 -1
  226. package/dist/llm-docs/tools/imap.js +1 -1
  227. package/dist/llm-docs/tools/imap.js.map +1 -1
  228. package/dist/llm-docs/tools/integrations.d.ts +1 -1
  229. package/dist/llm-docs/tools/integrations.d.ts.map +1 -1
  230. package/dist/llm-docs/tools/integrations.js +1 -1
  231. package/dist/llm-docs/tools/integrations.js.map +1 -1
  232. package/dist/llm-docs/tools/network.d.ts +1 -1
  233. package/dist/llm-docs/tools/network.d.ts.map +1 -1
  234. package/dist/llm-docs/tools/network.js +1 -1
  235. package/dist/llm-docs/tools/network.js.map +1 -1
  236. package/dist/llm-docs/tools/plot.d.ts +1 -1
  237. package/dist/llm-docs/tools/plot.d.ts.map +1 -1
  238. package/dist/llm-docs/tools/plot.js +1 -1
  239. package/dist/llm-docs/tools/plot.js.map +1 -1
  240. package/dist/llm-docs/tools/smtp.d.ts +1 -1
  241. package/dist/llm-docs/tools/smtp.d.ts.map +1 -1
  242. package/dist/llm-docs/tools/smtp.js +1 -1
  243. package/dist/llm-docs/tools/smtp.js.map +1 -1
  244. package/dist/llm-docs/tools/tasks.d.ts +1 -1
  245. package/dist/llm-docs/tools/tasks.d.ts.map +1 -1
  246. package/dist/llm-docs/tools/tasks.js +1 -1
  247. package/dist/llm-docs/tools/tasks.js.map +1 -1
  248. package/dist/llm-docs/tools/twists.d.ts +1 -1
  249. package/dist/llm-docs/tools/twists.d.ts.map +1 -1
  250. package/dist/llm-docs/tools/twists.js +1 -1
  251. package/dist/llm-docs/tools/twists.js.map +1 -1
  252. package/dist/llm-docs/twist-guide-template.d.ts +1 -1
  253. package/dist/llm-docs/twist-guide-template.d.ts.map +1 -1
  254. package/dist/llm-docs/twist-guide-template.js +1 -1
  255. package/dist/llm-docs/twist-guide-template.js.map +1 -1
  256. package/dist/llm-docs/twist.d.ts +1 -1
  257. package/dist/llm-docs/twist.d.ts.map +1 -1
  258. package/dist/llm-docs/twist.js +1 -1
  259. package/dist/llm-docs/twist.js.map +1 -1
  260. package/dist/plot.d.ts +15 -8
  261. package/dist/plot.d.ts.map +1 -1
  262. package/dist/plot.js.map +1 -1
  263. package/dist/tool.d.ts +4 -4
  264. package/dist/tool.js +4 -4
  265. package/dist/tools/ai.d.ts +12 -13
  266. package/dist/tools/ai.d.ts.map +1 -1
  267. package/dist/tools/ai.js +8 -9
  268. package/dist/tools/ai.js.map +1 -1
  269. package/dist/tools/callbacks.d.ts +1 -1
  270. package/dist/tools/files.d.ts +2 -2
  271. package/dist/tools/imap.d.ts +1 -1
  272. package/dist/tools/imap.js +1 -1
  273. package/dist/tools/integrations.d.ts +2 -1
  274. package/dist/tools/integrations.d.ts.map +1 -1
  275. package/dist/tools/network.d.ts +5 -5
  276. package/dist/tools/plot.d.ts +42 -37
  277. package/dist/tools/plot.d.ts.map +1 -1
  278. package/dist/tools/plot.js +16 -12
  279. package/dist/tools/plot.js.map +1 -1
  280. package/dist/tools/smtp.d.ts +1 -1
  281. package/dist/tools/smtp.js +1 -1
  282. package/dist/tools/tasks.d.ts +6 -8
  283. package/dist/tools/tasks.d.ts.map +1 -1
  284. package/dist/tools/tasks.js +5 -7
  285. package/dist/tools/tasks.js.map +1 -1
  286. package/dist/tools/twists.d.ts +15 -14
  287. package/dist/tools/twists.d.ts.map +1 -1
  288. package/dist/tools/twists.js +2 -2
  289. package/dist/tools/twists.js.map +1 -1
  290. package/dist/twist-guide.d.ts +1 -1
  291. package/dist/twist-guide.d.ts.map +1 -1
  292. package/dist/twist.d.ts +2 -2
  293. package/dist/twist.js +2 -2
  294. package/package.json +6 -1
  295. package/src/connector.ts +56 -16
  296. package/src/facets.ts +40 -0
  297. package/src/llm-docs/connector.ts +1 -1
  298. package/src/llm-docs/facets.ts +8 -0
  299. package/src/llm-docs/index.ts +2 -0
  300. package/src/llm-docs/plot.ts +1 -1
  301. package/src/llm-docs/tool.ts +1 -1
  302. package/src/llm-docs/tools/ai.ts +1 -1
  303. package/src/llm-docs/tools/callbacks.ts +1 -1
  304. package/src/llm-docs/tools/files.ts +1 -1
  305. package/src/llm-docs/tools/imap.ts +1 -1
  306. package/src/llm-docs/tools/integrations.ts +1 -1
  307. package/src/llm-docs/tools/network.ts +1 -1
  308. package/src/llm-docs/tools/plot.ts +1 -1
  309. package/src/llm-docs/tools/smtp.ts +1 -1
  310. package/src/llm-docs/tools/tasks.ts +1 -1
  311. package/src/llm-docs/tools/twists.ts +1 -1
  312. package/src/llm-docs/twist-guide-template.ts +1 -1
  313. package/src/llm-docs/twist.ts +1 -1
  314. package/src/plot.ts +15 -8
  315. package/src/tool.ts +4 -4
  316. package/src/tools/ai.ts +12 -13
  317. package/src/tools/callbacks.ts +1 -1
  318. package/src/tools/files.ts +2 -2
  319. package/src/tools/imap.ts +1 -1
  320. package/src/tools/integrations.ts +2 -1
  321. package/src/tools/network.ts +5 -5
  322. package/src/tools/plot.ts +42 -37
  323. package/src/tools/smtp.ts +1 -1
  324. package/src/tools/tasks.ts +6 -8
  325. package/src/tools/twists.ts +15 -14
  326. package/src/twist.ts +2 -2
  327. package/dist/docs/media/MULTI_USER_AUTH.md +0 -116
  328. package/dist/docs/media/SYNC_STRATEGIES.md +0 -818
@@ -0,0 +1,373 @@
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>SYNC_STRATEGIES | Creating Plot Twists</title><link rel="icon" href="../assets/favicon.svg" type="image/svg+xml"/><meta name="description" content="Documentation for Creating Plot Twists"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="/" class="title">Creating Plot Twists</a><div id="tsd-toolbar-links"><a href="https://plot.day">Plot</a><a href="https://github.com/plotday/plot">GitHub</a><a href="https://www.npmjs.com/package/@plotday/twister">NPM</a></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">SYNC_STRATEGIES</a></li></ul></div><div class="tsd-panel tsd-typography"><h1 id="sync-strategies" class="tsd-anchor-link">Sync Strategies<a href="#sync-strategies" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h1><p>This guide explains good ways to build connectors that sync other services with Plot. Choosing the right strategy depends on whether you need to update items, deduplicate them, or simply create them once.</p>
2
+ <h2 id="table-of-contents" class="tsd-anchor-link">Table of Contents<a href="#table-of-contents" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><ul>
3
+ <li><a href="#overview">Overview</a></li>
4
+ <li><a href="#strategy-1-create-once-fire-and-forget">Strategy 1: Create Once (Fire and Forget)</a></li>
5
+ <li><a href="#strategy-2-upsert-via-source-and-key-recommended">Strategy 2: Upsert via Source and Key (Recommended)</a></li>
6
+ <li><a href="#strategy-3-generate-and-store-ids-advanced">Strategy 3: Generate and Store IDs (Advanced)</a></li>
7
+ <li><a href="#tags-and-reactions">Tags and Reactions</a></li>
8
+ <li><a href="#initial-vs-incremental-sync">Initial vs Incremental Sync</a></li>
9
+ <li><a href="#choosing-the-right-strategy">Choosing the Right Strategy</a></li>
10
+ </ul>
11
+ <h2 id="overview" class="tsd-anchor-link">Overview<a href="#overview" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Plot provides three main strategies for managing threads, links, and notes:</p>
12
+ <table>
13
+ <thead>
14
+ <tr>
15
+ <th>Strategy</th>
16
+ <th>Use Case</th>
17
+ <th>Complexity</th>
18
+ <th>Deduplication</th>
19
+ <th>Updates</th>
20
+ </tr>
21
+ </thead>
22
+ <tbody>
23
+ <tr>
24
+ <td><strong>Create Once</strong></td>
25
+ <td>One-time notifications, transient events</td>
26
+ <td>Low</td>
27
+ <td>None</td>
28
+ <td>No</td>
29
+ </tr>
30
+ <tr>
31
+ <td><strong>Upsert via Source/Key</strong></td>
32
+ <td>Most integrations (calendars, tasks, issues)</td>
33
+ <td>Low</td>
34
+ <td>Automatic</td>
35
+ <td>Yes</td>
36
+ </tr>
37
+ <tr>
38
+ <td><strong>Generate and Store IDs</strong></td>
39
+ <td>Complex transformations, multiple items per source</td>
40
+ <td>High</td>
41
+ <td>Manual</td>
42
+ <td>Yes</td>
43
+ </tr>
44
+ </tbody>
45
+ </table>
46
+ <p><strong>Recommended for most use cases</strong>: Strategy 2 (Upsert via Source/Key)</p>
47
+ <h2 id="strategy-1-create-once-fire-and-forget" class="tsd-anchor-link">Strategy 1: Create Once (Fire and Forget)<a href="#strategy-1-create-once-fire-and-forget" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><h3 id="when-to-use" class="tsd-anchor-link">When to Use<a href="#when-to-use" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Use this strategy when:</p>
48
+ <ul>
49
+ <li>Items are created once and never need updates</li>
50
+ <li>Duplicates are acceptable or expected</li>
51
+ <li>You're creating notifications, alerts, or transient events</li>
52
+ <li>The external system doesn't provide stable identifiers</li>
53
+ </ul>
54
+ <h3 id="how-it-works" class="tsd-anchor-link">How It Works<a href="#how-it-works" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Simply create threads and notes without specifying <code>id</code> or <code>source</code> fields. Plot will generate unique IDs automatically.</p>
55
+ <h3 id="example-simple-notification" class="tsd-anchor-link">Example: Simple Notification<a href="#example-simple-notification" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">export</span><span class="hl-1"> </span><span class="hl-0">default</span><span class="hl-1"> </span><span class="hl-4">class</span><span class="hl-1"> </span><span class="hl-5">NotificationTwist</span><span class="hl-1"> </span><span class="hl-4">extends</span><span class="hl-1"> </span><span class="hl-5">Twist</span><span class="hl-1">&lt;</span><span class="hl-5">NotificationTwist</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">sendAlert</span><span class="hl-1">(</span><span class="hl-2">title</span><span class="hl-1">: </span><span class="hl-5">string</span><span class="hl-1">, </span><span class="hl-2">message</span><span class="hl-1">: </span><span class="hl-5">string</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-5">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-7">// Create a simple thread with one note</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">title</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">message</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
56
+ </code><button type="button">Copy</button></pre>
57
+
58
+ <h3 id="pros-and-cons" class="tsd-anchor-link">Pros and Cons<a href="#pros-and-cons" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><strong>Pros:</strong></p>
59
+ <ul>
60
+ <li>Simplest approach</li>
61
+ <li>No storage overhead</li>
62
+ <li>No external API lookups needed</li>
63
+ <li>Fast execution</li>
64
+ </ul>
65
+ <p><strong>Cons:</strong></p>
66
+ <ul>
67
+ <li>No deduplication</li>
68
+ <li>Cannot update existing items</li>
69
+ <li>Can create duplicates if called multiple times</li>
70
+ </ul>
71
+ <h2 id="strategy-2-upsert-via-source-and-key-recommended" class="tsd-anchor-link">Strategy 2: Upsert via Source and Key (Recommended)<a href="#strategy-2-upsert-via-source-and-key-recommended" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><h3 id="when-to-use-1" class="tsd-anchor-link">When to Use<a href="#when-to-use-1" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Use this strategy when:</p>
72
+ <ul>
73
+ <li>You're integrating with external systems that provide stable URLs or IDs</li>
74
+ <li>Items need to be updated when the external source changes</li>
75
+ <li>You want automatic deduplication without manual tracking</li>
76
+ <li>You're syncing calendars, tasks, issues, messages, or similar entities</li>
77
+ </ul>
78
+ <h3 id="how-it-works-1" class="tsd-anchor-link">How It Works<a href="#how-it-works-1" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><strong>For Links:</strong>
79
+ Connectors save data with <code>integrations.saveLink()</code> (or the batch <code>saveLinks()</code>), passing a <code>NewLinkWithNotes</code>. Set the <code>source</code> field to a stable identifier (or list canonical aliases in <code>sources</code>). When you save a link whose source the user already has, Plot will <strong>update</strong> the existing thread+link instead of creating a duplicate.</p>
80
+ <p><strong>For Notes:</strong>
81
+ Use the <code>key</code> field on each note to enable upserts. When you save a note with a key that already exists on the thread, Plot will <strong>update</strong> that note instead of creating a duplicate.</p>
82
+ <h3 id="link-upserts" class="tsd-anchor-link">Link Upserts<a href="#link-upserts" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>The <code>source</code> field should be:</p>
83
+ <ul>
84
+ <li>A stable identifier in a namespaced format (e.g., <code>linear:issue:&lt;uuid&gt;</code>, <code>gmail:thread-id-123</code>) — use immutable ids, and put the human-facing URL in <code>sourceUrl</code></li>
85
+ <li><strong>Globally unique for the logical external item</strong> — see the cross-user dedup note below</li>
86
+ </ul>
87
+ <blockquote>
88
+ <p><strong>Cross-user dedup:</strong> Two instances of the same connector (run by two different Plot users) that emit the same <code>source</code> for the same external item will converge on a <strong>single shared thread</strong>. This is how two users on the same Gmail message, calendar event, or Linear issue see one thread rather than two.</p>
89
+ <p>This means <code>source</code> must not merely be unique within one user's account — it must be globally unique for the item. If an external id is workspace- or tenant-scoped (Attio record ids, PostHog distinct_ids, Outlook event ids, Fellow note ids, etc.), include the workspace/tenant/mailbox id as a qualifier: <code>attio:&lt;workspaceId&gt;:person:&lt;recordId&gt;</code>, not <code>attio:person:&lt;recordId&gt;</code>. See <code>connectors/AGENTS.md</code> → &quot;<code>source</code> — idempotency + cross-user dedup&quot; for the full guidance.</p>
90
+ </blockquote>
91
+ <p>For cross-connector bundling, use the plural <code>sources</code> array instead: any element shared with another link's <code>sources</code> bundles the two links into the same thread. For example, every calendar connector emits <code>icaluid:&lt;iCalUID&gt;</code> so a meeting-notes connector can attach onto the same event thread by emitting the same alias. (<code>source</code> is the single-value shorthand for <code>sources</code> and is kept for backward compatibility; the runtime normalizes between them.)</p>
92
+ <h3 id="example-calendar-event-sync" class="tsd-anchor-link">Example: Calendar Event Sync<a href="#example-calendar-event-sync" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">export</span><span class="hl-1"> </span><span class="hl-0">default</span><span class="hl-1"> </span><span class="hl-4">class</span><span class="hl-1"> </span><span class="hl-5">GoogleCalendarConnector</span><span class="hl-1"> </span><span class="hl-4">extends</span><span class="hl-1"> </span><span class="hl-5">Connector</span><span class="hl-1">&lt;</span><span class="hl-5">GoogleCalendarConnector</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">syncEvent</span><span class="hl-1">(</span><span class="hl-2">event</span><span class="hl-1">: </span><span class="hl-5">calendar_v3</span><span class="hl-1">.</span><span class="hl-5">Schema$Event</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-5">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">notes</span><span class="hl-1">: </span><span class="hl-5">Omit</span><span class="hl-1">&lt;</span><span class="hl-5">NewNote</span><span class="hl-1">, </span><span class="hl-3">&quot;thread&quot;</span><span class="hl-1">&gt;[] = [];</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Add description as an upsertable note</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">description</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">notes</span><span class="hl-1">.</span><span class="hl-6">push</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-7">// Use a key for this specific note type</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">description</span><span class="hl-1">,</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">link</span><span class="hl-1">: </span><span class="hl-5">NewLinkWithNotes</span><span class="hl-1"> = {</span><br/><span class="hl-1"> </span><span class="hl-7">// Stable, cross-connector identifier for the event</span><br/><span class="hl-1"> </span><span class="hl-2">sources:</span><span class="hl-1"> [</span><span class="hl-3">`icaluid:</span><span class="hl-4">${</span><span class="hl-2">event</span><span class="hl-13">.</span><span class="hl-2">iCalUID</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">],</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;event&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">summary</span><span class="hl-1"> || </span><span class="hl-3">&quot;(No title)&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">sourceUrl:</span><span class="hl-1"> </span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">htmlLink</span><span class="hl-1"> ?? </span><span class="hl-4">null</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">schedules:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">start:</span><span class="hl-1"> </span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">start</span><span class="hl-1">?.</span><span class="hl-2">dateTime</span><span class="hl-1"> || </span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">start</span><span class="hl-1">?.</span><span class="hl-2">date</span><span class="hl-1"> || </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">(),</span><br/><span class="hl-1"> </span><span class="hl-2">end:</span><span class="hl-1"> </span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">end</span><span class="hl-1">?.</span><span class="hl-2">dateTime</span><span class="hl-1"> || </span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">end</span><span class="hl-1">?.</span><span class="hl-2">date</span><span class="hl-1"> || </span><span class="hl-4">undefined</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> </span><span class="hl-2">notes</span><span class="hl-1">,</span><br/><span class="hl-1"> };</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Create or update the thread+link pair</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">integrations</span><span class="hl-1">.</span><span class="hl-6">saveLink</span><span class="hl-1">(</span><span class="hl-2">link</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
93
+ </code><button type="button">Copy</button></pre>
94
+
95
+ <p><strong>How it works:</strong></p>
96
+ <ol>
97
+ <li>First sync: A thread+link with <code>sources: [&quot;icaluid:...&quot;]</code> is created with its description note and schedule</li>
98
+ <li>Event updated externally: Same source is used, so Plot updates the existing thread instead of creating a duplicate</li>
99
+ <li>Description changes: Note with <code>key: &quot;description&quot;</code> is updated</li>
100
+ <li>No duplicates created, no manual ID tracking needed</li>
101
+ </ol>
102
+ <h3 id="example-taskissue-sync" class="tsd-anchor-link">Example: Task/Issue Sync<a href="#example-taskissue-sync" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">export</span><span class="hl-1"> </span><span class="hl-0">default</span><span class="hl-1"> </span><span class="hl-4">class</span><span class="hl-1"> </span><span class="hl-5">LinearConnector</span><span class="hl-1"> </span><span class="hl-4">extends</span><span class="hl-1"> </span><span class="hl-5">Connector</span><span class="hl-1">&lt;</span><span class="hl-5">LinearConnector</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">syncIssue</span><span class="hl-1">(</span><span class="hl-2">issue</span><span class="hl-1">: </span><span class="hl-5">LinearIssue</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-5">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">notes</span><span class="hl-1">: </span><span class="hl-5">Omit</span><span class="hl-1">&lt;</span><span class="hl-5">NewNote</span><span class="hl-1">, </span><span class="hl-3">&quot;thread&quot;</span><span class="hl-1">&gt;[] = [];</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Description note with upsert</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">description</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">notes</span><span class="hl-1">.</span><span class="hl-6">push</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">description</span><span class="hl-1">,</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">link</span><span class="hl-1">: </span><span class="hl-5">NewLinkWithNotes</span><span class="hl-1"> = {</span><br/><span class="hl-1"> </span><span class="hl-7">// Use the immutable issue ID, not the (mutable) URL slug</span><br/><span class="hl-1"> </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">`linear:issue:</span><span class="hl-4">${</span><span class="hl-2">issue</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">sourceUrl:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">url</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;issue&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-7">// Matches a statuses[].status entry in this connector&#39;s linkTypes</span><br/><span class="hl-1"> </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">state</span><span class="hl-1">.</span><span class="hl-2">type</span><span class="hl-1"> === </span><span class="hl-3">&quot;completed&quot;</span><span class="hl-1"> ? </span><span class="hl-3">&quot;done&quot;</span><span class="hl-1"> : </span><span class="hl-3">&quot;open&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">`</span><span class="hl-4">${</span><span class="hl-2">issue</span><span class="hl-13">.</span><span class="hl-2">identifier</span><span class="hl-4">}</span><span class="hl-3">: </span><span class="hl-4">${</span><span class="hl-2">issue</span><span class="hl-13">.</span><span class="hl-2">title</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">meta:</span><span class="hl-1"> { </span><span class="hl-2">issueId:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1">, </span><span class="hl-2">issueKey:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">identifier</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">notes</span><span class="hl-1">,</span><br/><span class="hl-1"> };</span><br/><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">integrations</span><span class="hl-1">.</span><span class="hl-6">saveLink</span><span class="hl-1">(</span><span class="hl-2">link</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
103
+ </code><button type="button">Copy</button></pre>
104
+
105
+ <h3 id="referencing-threads-when-creating-notes" class="tsd-anchor-link">Referencing Threads When Creating Notes<a href="#referencing-threads-when-creating-notes" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>When creating a note separately (not as part of <code>NewLinkWithNotes</code>), reference the thread by its source. Connectors typically re-run <code>saveLink</code> with the new notes included (the link upserts, the notes upsert by key); twists with the Plot tool can address the thread directly:</p>
106
+ <pre><code class="typescript"><span class="hl-7">// Add a comment to an existing thread (Plot tool)</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createNote</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">thread:</span><span class="hl-1"> { </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">&quot;github:user/repo/issue:42&quot;</span><span class="hl-1"> },</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">`comment-</span><span class="hl-4">${</span><span class="hl-2">comment</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, </span><span class="hl-7">// Unique key per comment</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">comment</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">,</span><br/><span class="hl-1">});</span>
107
+ </code><button type="button">Copy</button></pre>
108
+
109
+ <h3 id="note-key-patterns" class="tsd-anchor-link">Note Key Patterns<a href="#note-key-patterns" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>The <code>key</code> field enables upsert behavior for notes. Choose keys based on your use case:</p>
110
+ <p><strong>Single instance notes</strong> (will be updated on each sync):</p>
111
+ <ul>
112
+ <li><code>key: &quot;description&quot;</code> - Main description/body</li>
113
+ <li><code>key: &quot;metadata&quot;</code> - Status, assignee, etc.</li>
114
+ <li><code>key: &quot;attendees&quot;</code> - Event attendees list</li>
115
+ </ul>
116
+ <p><strong>Multiple instance notes</strong> (use unique keys):</p>
117
+ <ul>
118
+ <li><code>key: &quot;comment-${commentId}&quot;</code> - Each comment has unique ID</li>
119
+ <li><code>key: &quot;attachment-${filename}&quot;</code> - Each attachment has unique name</li>
120
+ <li><code>key: &quot;change-${timestamp}&quot;</code> - Each change log entry</li>
121
+ </ul>
122
+ <p><strong>No key</strong> (creates new note every time):</p>
123
+ <ul>
124
+ <li>Omit <code>key</code> field when you want new notes created on each sync</li>
125
+ <li>Useful for chat messages, activity logs, or append-only data</li>
126
+ </ul>
127
+ <h3 id="pros-and-cons-1" class="tsd-anchor-link">Pros and Cons<a href="#pros-and-cons-1" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><strong>Pros:</strong></p>
128
+ <ul>
129
+ <li>Automatic deduplication</li>
130
+ <li>No storage overhead for ID mappings</li>
131
+ <li>No need to look up existing items before creating</li>
132
+ <li>Clean, maintainable code</li>
133
+ <li>The human-facing URL still surfaces via <code>sourceUrl</code> (user-friendly)</li>
134
+ </ul>
135
+ <p><strong>Cons:</strong></p>
136
+ <ul>
137
+ <li>Requires stable identifiers from external system</li>
138
+ <li>One Plot thread per external source item</li>
139
+ <li>Cannot create multiple Plot items from single source item</li>
140
+ </ul>
141
+ <h2 id="strategy-3-generate-and-store-ids-advanced" class="tsd-anchor-link">Strategy 3: Generate and Store IDs (Advanced)<a href="#strategy-3-generate-and-store-ids-advanced" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><h3 id="when-to-use-2" class="tsd-anchor-link">When to Use<a href="#when-to-use-2" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Use this strategy when:</p>
142
+ <ul>
143
+ <li>You need to create multiple Plot threads from a single external item</li>
144
+ <li>External system doesn't provide stable identifiers</li>
145
+ <li>You need complex transformations or splitting</li>
146
+ <li>Source-based upserts aren't flexible enough for your use case</li>
147
+ </ul>
148
+ <p>This strategy uses the Plot tool (<code>createThread</code> with explicit <code>id</code>s), so it applies to twists. Connectors save through <code>integrations.saveLink()</code> and should use Strategy 2.</p>
149
+ <h3 id="how-it-works-2" class="tsd-anchor-link">How It Works<a href="#how-it-works-2" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><ol>
150
+ <li>Generate a unique ID using <code>Uuid.Generate()</code></li>
151
+ <li>Store the mapping between external ID and Plot ID</li>
152
+ <li>Look up existing IDs before creating items</li>
153
+ <li>Use stored IDs when updating</li>
154
+ </ol>
155
+ <h3 id="example-multiple-threads-from-single-source" class="tsd-anchor-link">Example: Multiple Threads from Single Source<a href="#example-multiple-threads-from-single-source" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-0">export</span><span class="hl-1"> </span><span class="hl-0">default</span><span class="hl-1"> </span><span class="hl-4">class</span><span class="hl-1"> </span><span class="hl-5">EmailTasksTwist</span><span class="hl-1"> </span><span class="hl-4">extends</span><span class="hl-1"> </span><span class="hl-5">Twist</span><span class="hl-1">&lt;</span><span class="hl-5">EmailTasksTwist</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-7">/**</span><br/><span class="hl-7"> * Creates separate Plot threads for an email thread and individual messages.</span><br/><span class="hl-7"> * One email thread can map to multiple Plot threads.</span><br/><span class="hl-7"> */</span><br/><span class="hl-1"> </span><span class="hl-4">async</span><span class="hl-1"> </span><span class="hl-6">syncThread</span><span class="hl-1">(</span><span class="hl-2">thread</span><span class="hl-1">: </span><span class="hl-5">GmailThread</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-5">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-7">// Check if we&#39;ve seen this thread before</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">threadKey</span><span class="hl-1"> = </span><span class="hl-3">`thread:</span><span class="hl-4">${</span><span class="hl-2">thread</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-4">let</span><span class="hl-1"> </span><span class="hl-2">plotThreadId</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">Uuid</span><span class="hl-1">&gt;(</span><span class="hl-2">threadKey</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Generate ID if this is a new thread</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (!</span><span class="hl-2">plotThreadId</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">plotThreadId</span><span class="hl-1"> = </span><span class="hl-2">Uuid</span><span class="hl-1">.</span><span class="hl-6">Generate</span><span class="hl-1">();</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-2">threadKey</span><span class="hl-1">, </span><span class="hl-2">plotThreadId</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Create/update the thread</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">plotThreadId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">thread</span><span class="hl-1">.</span><span class="hl-2">snippet</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-7">// Note: we use `id` instead of `source` for manual control</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Create separate threads for each important message in the email thread</span><br/><span class="hl-1"> </span><span class="hl-0">for</span><span class="hl-1"> (</span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">message</span><span class="hl-1"> </span><span class="hl-4">of</span><span class="hl-1"> </span><span class="hl-2">thread</span><span class="hl-1">.</span><span class="hl-2">messages</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">isImportantMessage</span><span class="hl-1">(</span><span class="hl-2">message</span><span class="hl-1">)) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">messageKey</span><span class="hl-1"> = </span><span class="hl-3">`message:</span><span class="hl-4">${</span><span class="hl-2">message</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-4">let</span><span class="hl-1"> </span><span class="hl-2">messageThreadId</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">Uuid</span><span class="hl-1">&gt;(</span><span class="hl-2">messageKey</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (!</span><span class="hl-2">messageThreadId</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">messageThreadId</span><span class="hl-1"> = </span><span class="hl-2">Uuid</span><span class="hl-1">.</span><span class="hl-6">Generate</span><span class="hl-1">();</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-2">messageKey</span><span class="hl-1">, </span><span class="hl-2">messageThreadId</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">createThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">messageThreadId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;action&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-3">`Reply to: </span><span class="hl-4">${</span><span class="hl-2">message</span><span class="hl-13">.</span><span class="hl-2">subject</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">message</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-4">private</span><span class="hl-1"> </span><span class="hl-6">isImportantMessage</span><span class="hl-1">(</span><span class="hl-2">message</span><span class="hl-1">: </span><span class="hl-5">GmailMessage</span><span class="hl-1">): </span><span class="hl-5">boolean</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-7">// Custom logic to determine if message needs a separate thread</span><br/><span class="hl-1"> </span><span class="hl-0">return</span><span class="hl-1"> </span><span class="hl-2">message</span><span class="hl-1">.</span><span class="hl-2">labelIds</span><span class="hl-1">?.</span><span class="hl-6">includes</span><span class="hl-1">(</span><span class="hl-3">&quot;IMPORTANT&quot;</span><span class="hl-1">) || </span><span class="hl-4">false</span><span class="hl-1">;</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
156
+ </code><button type="button">Copy</button></pre>
157
+
158
+ <h3 id="storage-patterns" class="tsd-anchor-link">Storage Patterns<a href="#storage-patterns" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><strong>Simple mapping:</strong></p>
159
+ <pre><code class="typescript"><span class="hl-7">// Store external ID → Plot ID</span><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">`external:</span><span class="hl-4">${</span><span class="hl-2">externalId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, </span><span class="hl-2">plotId</span><span class="hl-1">);</span><br/><br/><span class="hl-7">// Retrieve</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">plotId</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">string</span><span class="hl-1">&gt;(</span><span class="hl-3">`external:</span><span class="hl-4">${</span><span class="hl-2">externalId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span>
160
+ </code><button type="button">Copy</button></pre>
161
+
162
+ <p><strong>Structured mapping:</strong></p>
163
+ <pre><code class="typescript"><span class="hl-4">interface</span><span class="hl-1"> </span><span class="hl-5">Mapping</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">plotId</span><span class="hl-1">: </span><span class="hl-5">string</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-2">externalId</span><span class="hl-1">: </span><span class="hl-5">string</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-2">lastSynced</span><span class="hl-1">: </span><span class="hl-5">string</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-2">syncCount</span><span class="hl-1">: </span><span class="hl-5">number</span><span class="hl-1">;</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-3">`mapping:</span><span class="hl-4">${</span><span class="hl-2">externalId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, </span><span class="hl-2">mapping</span><span class="hl-1">);</span>
164
+ </code><button type="button">Copy</button></pre>
165
+
166
+ <h3 id="lookup-and-update-pattern" class="tsd-anchor-link">Lookup and Update Pattern<a href="#lookup-and-update-pattern" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncItem</span><span class="hl-1">(</span><span class="hl-2">externalItem</span><span class="hl-1">: </span><span class="hl-2">ExternalItem</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-4">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> const </span><span class="hl-2">key</span><span class="hl-1"> = </span><span class="hl-3">`item:</span><span class="hl-4">${</span><span class="hl-2">externalItem</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">;</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Look up existing Plot ID</span><br/><span class="hl-1"> let </span><span class="hl-2">plotId</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">Uuid</span><span class="hl-1">&gt;(</span><span class="hl-2">key</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Generate new ID if not found</span><br/><span class="hl-1"> </span><span class="hl-6">if</span><span class="hl-1"> (!</span><span class="hl-2">plotId</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-2">plotId</span><span class="hl-1"> = </span><span class="hl-2">Uuid</span><span class="hl-1">.</span><span class="hl-6">Generate</span><span class="hl-1">();</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">set</span><span class="hl-1">(</span><span class="hl-2">key</span><span class="hl-1">, </span><span class="hl-2">plotId</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Create or update using the ID</span><br/><span class="hl-1"> await this.tools.plot.createThread({</span><br/><span class="hl-1"> </span><span class="hl-15">id</span><span class="hl-1">: </span><span class="hl-2">plotId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-15">type</span><span class="hl-1">: </span><span class="hl-3">&quot;action&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-15">title</span><span class="hl-1">: </span><span class="hl-2">externalItem</span><span class="hl-1">.</span><span class="hl-2">title</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-7">// ... other fields</span><br/><span class="hl-1"> });</span><br/><span class="hl-1">}</span>
167
+ </code><button type="button">Copy</button></pre>
168
+
169
+ <h3 id="pros-and-cons-2" class="tsd-anchor-link">Pros and Cons<a href="#pros-and-cons-2" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><strong>Pros:</strong></p>
170
+ <ul>
171
+ <li>Maximum flexibility</li>
172
+ <li>Can create multiple Plot items per external item</li>
173
+ <li>Works without stable external identifiers</li>
174
+ <li>Full control over ID lifecycle</li>
175
+ </ul>
176
+ <p><strong>Cons:</strong></p>
177
+ <ul>
178
+ <li>Requires storage for mappings</li>
179
+ <li>Needs lookup before each create/update</li>
180
+ <li>More code to maintain</li>
181
+ <li>Slower due to additional storage operations</li>
182
+ <li>Must manage cleanup of old mappings</li>
183
+ </ul>
184
+ <h2 id="tags-and-reactions" class="tsd-anchor-link">Tags and Reactions<a href="#tags-and-reactions" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Plot's <code>tags</code> are a small set of system &quot;compute&quot; tags (<code>Tag.Todo</code>, <code>Tag.Done</code>, <code>Tag.Twist</code> from <code>@plotday/twister/tag</code>) — not free-form labels. External system labels have no tag equivalent: keep them in the link's <code>meta</code> (or render them in a note), and map completion-style state to the link's <code>status</code>. Emoji reactions round-trip through the separate <code>reactions</code> field on threads and notes.</p>
185
+ <h3 id="syncing-reactions" class="tsd-anchor-link">Syncing Reactions<a href="#syncing-reactions" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Include reactions in the note upsert, keyed by emoji with the actors who reacted:</p>
186
+ <pre><code class="typescript"><span class="hl-15">notes</span><span class="hl-1">: [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">`comment-</span><span class="hl-4">${</span><span class="hl-2">comment</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">comment</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">reactions:</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-3">&quot;👍&quot;</span><span class="hl-2">:</span><span class="hl-1"> [{ </span><span class="hl-2">email:</span><span class="hl-1"> </span><span class="hl-3">&quot;amy@example.com&quot;</span><span class="hl-1">, </span><span class="hl-2">name:</span><span class="hl-1"> </span><span class="hl-3">&quot;Amy&quot;</span><span class="hl-1"> }],</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> },</span><br/><span class="hl-1">],</span>
187
+ </code><button type="button">Copy</button></pre>
188
+
189
+ <p>To remove a reaction for an actor, omit them from that emoji's list; an empty list removes the reaction entirely. Omit an emoji to leave it untouched.</p>
190
+ <h3 id="updating-tags-from-a-twist" class="tsd-anchor-link">Updating Tags from a Twist<a href="#updating-tags-from-a-twist" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Twists with the Plot tool toggle their own compute tags via <code>twistTags</code> — <code>true</code> adds the tag, <code>false</code> removes it, and other actors' tags are untouched:</p>
191
+ <pre><code class="typescript"><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">plot</span><span class="hl-1">.</span><span class="hl-6">updateThread</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">threadId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">twistTags:</span><span class="hl-1"> { </span><span class="hl-2">[Tag.Todo]:</span><span class="hl-1"> </span><span class="hl-4">true</span><span class="hl-1"> },</span><br/><span class="hl-1">});</span>
192
+ </code><button type="button">Copy</button></pre>
193
+
194
+ <p>The same <code>twistTags</code> field exists on <code>updateNote()</code> for note-level tags.</p>
195
+ <h3 id="completion-state" class="tsd-anchor-link">Completion State<a href="#completion-state" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>For connectors, &quot;done-ness&quot; lives on the link's <code>status</code> (declared in <code>linkTypes</code> with <code>done: true</code> on completed statuses), not on a tag:</p>
196
+ <pre><code class="typescript"><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">integrations</span><span class="hl-1">.</span><span class="hl-6">saveLink</span><span class="hl-1">({</span><br/><span class="hl-1"> </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">`linear:issue:</span><span class="hl-4">${</span><span class="hl-2">issue</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">status:</span><span class="hl-1"> </span><span class="hl-2">issue</span><span class="hl-1">.</span><span class="hl-2">completed</span><span class="hl-1"> ? </span><span class="hl-3">&quot;done&quot;</span><span class="hl-1"> : </span><span class="hl-3">&quot;open&quot;</span><span class="hl-1">,</span><br/><span class="hl-1">});</span>
197
+ </code><button type="button">Copy</button></pre>
198
+
199
+ <h2 id="initial-vs-incremental-sync" class="tsd-anchor-link">Initial vs Incremental Sync<a href="#initial-vs-incremental-sync" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>When syncing items from external systems, it's critical to distinguish between initial sync (first import) and incremental sync (ongoing updates). This prevents notification spam and properly handles archived state.</p>
200
+ <h3 id="the-flag-pattern" class="tsd-anchor-link">The <code>initialSync</code> Flag Pattern<a href="#the-flag-pattern" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>All sync-based connectors should track whether they're performing an initial sync or incremental sync:</p>
201
+ <table>
202
+ <thead>
203
+ <tr>
204
+ <th>Field</th>
205
+ <th>Initial Sync</th>
206
+ <th>Incremental Sync</th>
207
+ <th>Reason</th>
208
+ </tr>
209
+ </thead>
210
+ <tbody>
211
+ <tr>
212
+ <td><code>unread</code></td>
213
+ <td><code>false</code></td>
214
+ <td><em>omit</em></td>
215
+ <td>Avoid notification overload from historical items; omitting uses the default (unread for users)</td>
216
+ </tr>
217
+ <tr>
218
+ <td><code>archived</code></td>
219
+ <td><code>false</code></td>
220
+ <td><em>omit</em></td>
221
+ <td>Unarchive on install, preserve user choice on updates</td>
222
+ </tr>
223
+ </tbody>
224
+ </table>
225
+ <h3 id="example-implementation" class="tsd-anchor-link">Example Implementation<a href="#example-implementation" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">startSync</span><span class="hl-1">(</span><span class="hl-2">channelId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-4">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-7">// Store initial sync state</span><br/><span class="hl-1"> await this.set(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channelId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">channelId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-15">initialSync</span><span class="hl-1">: </span><span class="hl-4">true</span><span class="hl-1">,</span><br/><span class="hl-1"> });</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Start first batch with initialSync = true</span><br/><span class="hl-1"> const </span><span class="hl-2">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">syncBatch</span><span class="hl-1">, </span><span class="hl-2">channelId</span><span class="hl-1">, </span><span class="hl-4">true</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-7">// runTask creates NEW execution with fresh ~1000 request limit</span><br/><span class="hl-1"> await this.runTask(callback);</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncBatch</span><span class="hl-1">(</span><span class="hl-2">channelId</span><span class="hl-1">: </span><span class="hl-2">string</span><span class="hl-1">, </span><span class="hl-2">initialSync</span><span class="hl-1">: </span><span class="hl-2">boolean</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-4">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> const </span><span class="hl-2">token</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">integrations</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">(</span><span class="hl-2">channelId</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-6">if</span><span class="hl-1"> (!</span><span class="hl-2">token</span><span class="hl-1">) return;</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Fetch events from external API (keep batch size reasonable to stay under request limit)</span><br/><span class="hl-1"> const { </span><span class="hl-2">events</span><span class="hl-1">, </span><span class="hl-2">hasMorePages</span><span class="hl-1"> } = await this.fetchEvents(</span><span class="hl-2">token</span><span class="hl-1">, channelId);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Save links with proper flags — one batched saveLinks call per page</span><br/><span class="hl-1"> const </span><span class="hl-2">links:</span><span class="hl-1"> </span><span class="hl-2">NewLinkWithNotes</span><span class="hl-1">[] = </span><span class="hl-2">events</span><span class="hl-1">.</span><span class="hl-6">map</span><span class="hl-1">((</span><span class="hl-2">event</span><span class="hl-1">) </span><span class="hl-4">=&gt;</span><span class="hl-1"> ({</span><br/><span class="hl-1"> </span><span class="hl-2">source:</span><span class="hl-1"> </span><span class="hl-3">`example:event:</span><span class="hl-4">${</span><span class="hl-2">event</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">sourceUrl:</span><span class="hl-1"> </span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">url</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">type:</span><span class="hl-1"> </span><span class="hl-3">&quot;event&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">title:</span><span class="hl-1"> </span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">title</span><span class="hl-1">,</span><br/><span class="hl-1"> ...(</span><span class="hl-2">initialSync</span><span class="hl-1"> ? { </span><span class="hl-2">unread:</span><span class="hl-1"> </span><span class="hl-4">false</span><span class="hl-1">, </span><span class="hl-2">archived:</span><span class="hl-1"> </span><span class="hl-4">false</span><span class="hl-1"> } : {}), </span><span class="hl-7">// omit both for incremental</span><br/><span class="hl-1"> </span><span class="hl-2">notes:</span><span class="hl-1"> </span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">description</span><br/><span class="hl-1"> ? [{ </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">, </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">description</span><span class="hl-1"> }]</span><br/><span class="hl-1"> : [],</span><br/><span class="hl-1"> }));</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">integrations</span><span class="hl-1">.</span><span class="hl-6">saveLinks</span><span class="hl-1">(</span><span class="hl-2">links</span><span class="hl-1">);</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Queue next batch or switch to incremental mode</span><br/><span class="hl-1"> </span><span class="hl-6">if</span><span class="hl-1"> (</span><span class="hl-2">hasMorePages</span><span class="hl-1">) {</span><br/><span class="hl-1"> const </span><span class="hl-2">callback</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">callback</span><span class="hl-1">(</span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">syncBatch</span><span class="hl-1">, </span><span class="hl-2">channelId</span><span class="hl-1">, </span><span class="hl-2">initialSync</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-7">// Each runTask creates NEW execution with fresh request limit</span><br/><span class="hl-1"> await this.runTask(callback);</span><br/><span class="hl-1"> } </span><span class="hl-2">else</span><span class="hl-1"> </span><span class="hl-6">if</span><span class="hl-1"> (</span><span class="hl-2">initialSync</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-7">// Initial sync complete, switch to incremental mode</span><br/><span class="hl-1"> await this.set(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channelId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">channelId</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-15">initialSync</span><span class="hl-1">: </span><span class="hl-4">false</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-15">lastSync</span><span class="hl-1">: </span><span class="hl-4">new</span><span class="hl-1"> </span><span class="hl-6">Date</span><span class="hl-1">().</span><span class="hl-6">toISOString</span><span class="hl-1">(),</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> </span><span class="hl-7">// Clear the &quot;syncing…&quot; indicator on the connection</span><br/><span class="hl-1"> await this.tools.integrations.channelSyncCompleted(channelId);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
226
+ </code><button type="button">Copy</button></pre>
227
+
228
+ <h3 id="why-this-matters" class="tsd-anchor-link">Why This Matters<a href="#why-this-matters" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p><strong>Initial sync (first import):</strong></p>
229
+ <ul>
230
+ <li>Threads are <strong>unarchived</strong> (<code>archived: false</code>) - gives user a fresh start</li>
231
+ <li>Threads are marked as <strong>read</strong> (<code>unread: false</code>) - prevents notification spam from bulk historical imports</li>
232
+ <li>Use case: When user first installs the connector or reconnects after disconnection</li>
233
+ </ul>
234
+ <p><strong>Incremental sync (ongoing updates):</strong></p>
235
+ <ul>
236
+ <li>New threads appear as <strong>unread</strong> (<code>unread</code> omitted — the default) - user gets notified of new items</li>
237
+ <li>Archived state is <strong>preserved</strong> (field omitted) - respects user's archiving decisions</li>
238
+ <li>Use case: Regular syncs after initial setup is complete</li>
239
+ </ul>
240
+ <p><strong>Reinstall behavior:</strong></p>
241
+ <ul>
242
+ <li>Acts as initial sync - previously archived threads are unarchived for fresh start</li>
243
+ <li>User gets a clean slate without notification overload</li>
244
+ </ul>
245
+ <h3 id="tracking-sync-state" class="tsd-anchor-link">Tracking Sync State<a href="#tracking-sync-state" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Store the <code>initialSync</code> flag in your sync state:</p>
246
+ <pre><code class="typescript"><span class="hl-4">interface</span><span class="hl-1"> </span><span class="hl-5">SyncState</span><span class="hl-1"> {</span><br/><span class="hl-1"> </span><span class="hl-2">channelId</span><span class="hl-1">: </span><span class="hl-5">string</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-2">initialSync</span><span class="hl-1">: </span><span class="hl-5">boolean</span><span class="hl-1">;</span><br/><span class="hl-1"> </span><span class="hl-2">lastSync</span><span class="hl-1">: </span><span class="hl-5">string</span><span class="hl-1"> | </span><span class="hl-5">null</span><span class="hl-1">;</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-7">// Check sync mode before each batch</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">state</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">get</span><span class="hl-1">&lt;</span><span class="hl-5">SyncState</span><span class="hl-1">&gt;(</span><span class="hl-3">`sync_state_</span><span class="hl-4">${</span><span class="hl-2">channelId</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">);</span><br/><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">initialSync</span><span class="hl-1"> = </span><span class="hl-2">state</span><span class="hl-1">?.</span><span class="hl-2">initialSync</span><span class="hl-1"> ?? </span><span class="hl-4">true</span><span class="hl-1">; </span><span class="hl-7">// Default to initial if not set</span>
247
+ </code><button type="button">Copy</button></pre>
248
+
249
+ <h2 id="choosing-the-right-strategy" class="tsd-anchor-link">Choosing the Right Strategy<a href="#choosing-the-right-strategy" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><p>Use this decision tree to select the appropriate strategy:</p>
250
+ <pre><code><span class="hl-2">Do</span><span class="hl-1"> </span><span class="hl-2">items</span><span class="hl-1"> </span><span class="hl-2">need</span><span class="hl-1"> </span><span class="hl-2">to</span><span class="hl-1"> </span><span class="hl-2">be</span><span class="hl-1"> </span><span class="hl-2">updated</span><span class="hl-1"> </span><span class="hl-2">after</span><span class="hl-1"> </span><span class="hl-2">creation</span><span class="hl-1">?</span><br/><span class="hl-1">├─ </span><span class="hl-2">No</span><br/><span class="hl-1">│ └─ </span><span class="hl-2">Use</span><span class="hl-1"> </span><span class="hl-2">Strategy</span><span class="hl-1"> </span><span class="hl-14">1</span><span class="hl-1"> (</span><span class="hl-2">Create</span><span class="hl-1"> </span><span class="hl-2">Once</span><span class="hl-1">)</span><br/><span class="hl-1">│ </span><span class="hl-2">Example</span><span class="hl-1">: </span><span class="hl-2">Alerts</span><span class="hl-1">, </span><span class="hl-2">one</span><span class="hl-1">-</span><span class="hl-2">time</span><span class="hl-1"> </span><span class="hl-2">notifications</span><br/><span class="hl-1">│</span><br/><span class="hl-1">└─ </span><span class="hl-2">Yes</span><br/><span class="hl-1"> │</span><br/><span class="hl-1"> </span><span class="hl-2">Does</span><span class="hl-1"> </span><span class="hl-2">the</span><span class="hl-1"> </span><span class="hl-2">external</span><span class="hl-1"> </span><span class="hl-2">system</span><span class="hl-1"> </span><span class="hl-2">provide</span><span class="hl-1"> </span><span class="hl-2">stable</span><span class="hl-1"> </span><span class="hl-2">URLs</span><span class="hl-1"> </span><span class="hl-2">or</span><span class="hl-1"> </span><span class="hl-2">IDs</span><span class="hl-1">?</span><br/><span class="hl-1"> ├─ </span><span class="hl-2">Yes</span><br/><span class="hl-1"> │ │</span><br/><span class="hl-1"> │ </span><span class="hl-2">Do</span><span class="hl-1"> </span><span class="hl-2">you</span><span class="hl-1"> </span><span class="hl-2">need</span><span class="hl-1"> </span><span class="hl-2">multiple</span><span class="hl-1"> </span><span class="hl-2">Plot</span><span class="hl-1"> </span><span class="hl-2">items</span><span class="hl-1"> </span><span class="hl-2">per</span><span class="hl-1"> </span><span class="hl-2">external</span><span class="hl-1"> </span><span class="hl-2">item</span><span class="hl-1">?</span><br/><span class="hl-1"> │ ├─ </span><span class="hl-2">No</span><br/><span class="hl-1"> │ │ └─ </span><span class="hl-2">Use</span><span class="hl-1"> </span><span class="hl-2">Strategy</span><span class="hl-1"> </span><span class="hl-14">2</span><span class="hl-1"> (</span><span class="hl-2">Upsert</span><span class="hl-1"> </span><span class="hl-2">via</span><span class="hl-1"> </span><span class="hl-2">Source</span><span class="hl-1">/</span><span class="hl-2">Key</span><span class="hl-1">) ⭐ </span><span class="hl-8">RECOMMENDED</span><br/><span class="hl-1"> │ │ </span><span class="hl-2">Example</span><span class="hl-1">: </span><span class="hl-2">Calendar</span><span class="hl-1"> </span><span class="hl-2">events</span><span class="hl-1">, </span><span class="hl-2">tasks</span><span class="hl-1">, </span><span class="hl-2">issues</span><br/><span class="hl-1"> │ │</span><br/><span class="hl-1"> │ └─ </span><span class="hl-2">Yes</span><br/><span class="hl-1"> │ └─ </span><span class="hl-2">Use</span><span class="hl-1"> </span><span class="hl-2">Strategy</span><span class="hl-1"> </span><span class="hl-14">3</span><span class="hl-1"> (</span><span class="hl-2">Generate</span><span class="hl-1"> </span><span class="hl-2">and</span><span class="hl-1"> </span><span class="hl-2">Store</span><span class="hl-1"> </span><span class="hl-2">IDs</span><span class="hl-1">)</span><br/><span class="hl-1"> │ </span><span class="hl-2">Example</span><span class="hl-1">: </span><span class="hl-2">Email</span><span class="hl-1"> </span><span class="hl-2">thread</span><span class="hl-1"> → </span><span class="hl-2">multiple</span><span class="hl-1"> </span><span class="hl-2">Plot</span><span class="hl-1"> </span><span class="hl-2">threads</span><br/><span class="hl-1"> │</span><br/><span class="hl-1"> └─ </span><span class="hl-2">No</span><br/><span class="hl-1"> └─ </span><span class="hl-2">Use</span><span class="hl-1"> </span><span class="hl-2">Strategy</span><span class="hl-1"> </span><span class="hl-14">3</span><span class="hl-1"> (</span><span class="hl-2">Generate</span><span class="hl-1"> </span><span class="hl-2">and</span><span class="hl-1"> </span><span class="hl-2">Store</span><span class="hl-1"> </span><span class="hl-2">IDs</span><span class="hl-1">)</span><br/><span class="hl-1"> </span><span class="hl-15">Example</span><span class="hl-1">: </span><span class="hl-2">Systems</span><span class="hl-1"> </span><span class="hl-2">without</span><span class="hl-1"> </span><span class="hl-2">stable</span><span class="hl-1"> </span><span class="hl-2">identifiers</span>
251
+ </code><button>Copy</button></pre>
252
+
253
+ <h3 id="common-use-cases" class="tsd-anchor-link">Common Use Cases<a href="#common-use-cases" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><table>
254
+ <thead>
255
+ <tr>
256
+ <th>Integration</th>
257
+ <th>Recommended Strategy</th>
258
+ <th>Rationale</th>
259
+ </tr>
260
+ </thead>
261
+ <tbody>
262
+ <tr>
263
+ <td>Google Calendar</td>
264
+ <td>Strategy 2</td>
265
+ <td>Events have stable <code>iCalUID</code>s (<code>icaluid:&lt;uid&gt;</code>)</td>
266
+ </tr>
267
+ <tr>
268
+ <td>Outlook Calendar</td>
269
+ <td>Strategy 2</td>
270
+ <td>Events have stable IDs (qualify with the mailbox ID)</td>
271
+ </tr>
272
+ <tr>
273
+ <td>Jira</td>
274
+ <td>Strategy 2</td>
275
+ <td>Issues have stable immutable IDs</td>
276
+ </tr>
277
+ <tr>
278
+ <td>Linear</td>
279
+ <td>Strategy 2</td>
280
+ <td>Issues have stable immutable IDs</td>
281
+ </tr>
282
+ <tr>
283
+ <td>Asana</td>
284
+ <td>Strategy 2</td>
285
+ <td>Tasks have stable IDs</td>
286
+ </tr>
287
+ <tr>
288
+ <td>GitHub Issues</td>
289
+ <td>Strategy 2</td>
290
+ <td>Issues have stable <code>owner/repo</code> + number IDs</td>
291
+ </tr>
292
+ <tr>
293
+ <td>Gmail (threads)</td>
294
+ <td>Strategy 2</td>
295
+ <td>One thread per Gmail thread; messages upsert as notes keyed by message ID</td>
296
+ </tr>
297
+ <tr>
298
+ <td>Slack (threads)</td>
299
+ <td>Strategy 2</td>
300
+ <td>Threads have stable channel:thread IDs</td>
301
+ </tr>
302
+ <tr>
303
+ <td>RSS Feeds</td>
304
+ <td>Strategy 2</td>
305
+ <td>Items usually have GUIDs or links</td>
306
+ </tr>
307
+ <tr>
308
+ <td>Webhooks</td>
309
+ <td>Strategy 1 or 2</td>
310
+ <td>Depends on whether updates are needed</td>
311
+ </tr>
312
+ <tr>
313
+ <td>Notifications</td>
314
+ <td>Strategy 1</td>
315
+ <td>Usually one-time, no updates needed</td>
316
+ </tr>
317
+ </tbody>
318
+ </table>
319
+ <h3 id="migration-between-strategies" class="tsd-anchor-link">Migration Between Strategies<a href="#migration-between-strategies" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>If you need to change strategies for an existing tool:</p>
320
+ <p><strong>From Strategy 1 to Strategy 2:</strong></p>
321
+ <ul>
322
+ <li>Existing items will remain as duplicates</li>
323
+ <li>New syncs will use source-based deduplication</li>
324
+ <li>Consider adding migration logic to clean up duplicates</li>
325
+ </ul>
326
+ <p><strong>From Strategy 3 to Strategy 2:</strong></p>
327
+ <ul>
328
+ <li>There is no way to attach a <code>source</code> to an existing thread that was created by ID, so old items can't be adopted into source-based upserts in place</li>
329
+ <li>Either keep the stored ID mapping for items created before the migration (and use Strategy 2 only for new items), or archive the old threads and re-sync them by source</li>
330
+ <li>Clean up mappings you no longer need with <code>this.clear(key)</code></li>
331
+ </ul>
332
+ <p><strong>From Strategy 2 to Strategy 3:</strong></p>
333
+ <ul>
334
+ <li>Existing threads will remain with their sources</li>
335
+ <li>New items can use generated IDs</li>
336
+ <li>Both can coexist if needed</li>
337
+ </ul>
338
+ <h2 id="best-practices" class="tsd-anchor-link">Best Practices<a href="#best-practices" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><h3 id="1-be-consistent-within-a-connector" class="tsd-anchor-link">1. Be Consistent Within a Connector<a href="#1-be-consistent-within-a-connector" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Choose one strategy per connector and stick with it. Mixing strategies in the same connector can lead to confusion and bugs.</p>
339
+ <h3 id="2-use-descriptive-keys" class="tsd-anchor-link">2. Use Descriptive Keys<a href="#2-use-descriptive-keys" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-7">// Good: descriptive, unique keys</span><br/><span class="hl-15">key</span><span class="hl-1">: </span><span class="hl-3">&quot;description&quot;</span><span class="hl-1">;</span><br/><span class="hl-15">key</span><span class="hl-1">: </span><span class="hl-3">&quot;metadata&quot;</span><span class="hl-1">;</span><br/><span class="hl-15">key</span><span class="hl-1">: </span><span class="hl-3">&quot;comment-${commentId}&quot;</span><span class="hl-1">;</span><br/><span class="hl-15">key</span><span class="hl-1">: </span><span class="hl-3">&quot;attachment-${filename}&quot;</span><span class="hl-1">;</span><br/><br/><span class="hl-7">// Bad: generic, collision-prone keys</span><br/><span class="hl-15">key</span><span class="hl-1">: </span><span class="hl-3">&quot;note&quot;</span><span class="hl-1">;</span><br/><span class="hl-15">key</span><span class="hl-1">: </span><span class="hl-3">&quot;data&quot;</span><span class="hl-1">;</span><br/><span class="hl-15">key</span><span class="hl-1">: </span><span class="hl-3">&quot;1&quot;</span><span class="hl-1">;</span>
340
+ </code><button type="button">Copy</button></pre>
341
+
342
+ <h3 id="3-handle-missing-sources-gracefully" class="tsd-anchor-link">3. Handle Missing Sources Gracefully<a href="#3-handle-missing-sources-gracefully" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="typescript"><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">source</span><span class="hl-1"> = </span><span class="hl-2">event</span><span class="hl-1">.</span><span class="hl-2">id</span><br/><span class="hl-1"> ? </span><span class="hl-3">`example:event:</span><span class="hl-4">${</span><span class="hl-2">event</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><br/><span class="hl-1"> : </span><span class="hl-3">`temp:</span><span class="hl-4">${</span><span class="hl-2">Uuid</span><span class="hl-13">.</span><span class="hl-6">Generate</span><span class="hl-13">()</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">;</span>
343
+ </code><button type="button">Copy</button></pre>
344
+
345
+ <h3 id="4-document-your-strategy" class="tsd-anchor-link">4. Document Your Strategy<a href="#4-document-your-strategy" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Add comments explaining which strategy you're using and why:</p>
346
+ <pre><code class="typescript"><span class="hl-7">/**</span><br/><span class="hl-7"> * Syncs calendar events using Strategy 2 (Upsert via Source).</span><br/><span class="hl-7"> * Each Google Calendar event has a stable iCalUID that serves as the source.</span><br/><span class="hl-7"> * Event details are stored as upsertable notes using keys.</span><br/><span class="hl-7"> */</span><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">syncEvents</span><span class="hl-1">(): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-4">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-7">// ...</span><br/><span class="hl-1">}</span>
347
+ </code><button type="button">Copy</button></pre>
348
+
349
+ <h3 id="5-clean-up-when-needed" class="tsd-anchor-link">5. Clean Up When Needed<a href="#5-clean-up-when-needed" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>For Strategy 3, implement cleanup for old mappings:</p>
350
+ <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">cleanupOldMappings</span><span class="hl-1">(): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-4">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> </span><span class="hl-7">// Remove mappings for items deleted externally</span><br/><span class="hl-1"> const </span><span class="hl-2">keys</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-2">tools</span><span class="hl-1">.</span><span class="hl-2">store</span><span class="hl-1">.</span><span class="hl-6">list</span><span class="hl-1">(</span><span class="hl-3">&quot;external:&quot;</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-6">for</span><span class="hl-1"> (</span><span class="hl-2">const</span><span class="hl-1"> </span><span class="hl-2">key</span><span class="hl-1"> </span><span class="hl-2">of</span><span class="hl-1"> </span><span class="hl-2">keys</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">externalId</span><span class="hl-1"> = </span><span class="hl-2">key</span><span class="hl-1">.</span><span class="hl-6">replace</span><span class="hl-1">(</span><span class="hl-3">&quot;external:&quot;</span><span class="hl-1">, </span><span class="hl-3">&quot;&quot;</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-4">const</span><span class="hl-1"> </span><span class="hl-8">exists</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">checkExternalItemExists</span><span class="hl-1">(</span><span class="hl-2">externalId</span><span class="hl-1">);</span><br/><span class="hl-1"> </span><span class="hl-0">if</span><span class="hl-1"> (!</span><span class="hl-2">exists</span><span class="hl-1">) {</span><br/><span class="hl-1"> </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-4">this</span><span class="hl-1">.</span><span class="hl-6">clear</span><span class="hl-1">(</span><span class="hl-2">key</span><span class="hl-1">);</span><br/><span class="hl-1"> }</span><br/><span class="hl-1"> }</span><br/><span class="hl-1">}</span>
351
+ </code><button type="button">Copy</button></pre>
352
+
353
+ <h3 id="6-avoid-race-conditions-in-two-way-sync" class="tsd-anchor-link">6. Avoid Race Conditions in Two-Way Sync<a href="#6-avoid-race-conditions-in-two-way-sync" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><p>When implementing two-way sync where items can be created in Plot and pushed to an external system (e.g. Notes becoming comments), update the link's <code>source</code> / <code>Note.key</code> <strong>after</strong> creating the external item. If the external system supports setting custom metadata, include the <code>Thread.id</code> / <code>Note.id</code> in the metadata when creating the external item. Then, when processing an incoming webhook, check for the Plot ID in the metadata first and use it if present.</p>
354
+ <p>This eliminates a race condition where a webhook for an item you're creating arrives before you've updated the link/note with the external key. Without this pattern, the webhook handler won't find the item by external key and may create a duplicate.</p>
355
+ <p>In a connector, return a <code>NoteWriteBackResult</code> from <code>onNoteCreated</code> — the runtime sets the key atomically and also records the external content as the sync baseline:</p>
356
+ <pre><code class="typescript"><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">onNoteCreated</span><span class="hl-1">(</span><span class="hl-2">note</span><span class="hl-1">: </span><span class="hl-2">Note</span><span class="hl-1">, </span><span class="hl-2">thread</span><span class="hl-1">: </span><span class="hl-2">Thread</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-2">NoteWriteBackResult</span><span class="hl-1"> | </span><span class="hl-4">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> const </span><span class="hl-2">externalComment</span><span class="hl-1"> = </span><span class="hl-0">await</span><span class="hl-1"> </span><span class="hl-2">externalApi</span><span class="hl-1">.</span><span class="hl-6">createComment</span><span class="hl-1">(</span><span class="hl-2">thread</span><span class="hl-1">.</span><span class="hl-2">meta</span><span class="hl-1">?.</span><span class="hl-2">externalItemId</span><span class="hl-1">, {</span><br/><span class="hl-1"> </span><span class="hl-2">body:</span><span class="hl-1"> </span><span class="hl-2">note</span><span class="hl-1">.</span><span class="hl-2">content</span><span class="hl-1"> ?? </span><span class="hl-3">&quot;&quot;</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">metadata:</span><span class="hl-1"> { </span><span class="hl-2">plotNoteId:</span><span class="hl-1"> </span><span class="hl-2">note</span><span class="hl-1">.</span><span class="hl-2">id</span><span class="hl-1"> }, </span><span class="hl-7">// Embed Plot ID for webhook correlation</span><br/><span class="hl-1"> });</span><br/><span class="hl-1"> </span><span class="hl-6">if</span><span class="hl-1"> (!</span><span class="hl-2">externalComment</span><span class="hl-1">?.id) return;</span><br/><span class="hl-1"> return {</span><br/><span class="hl-1"> </span><span class="hl-15">key</span><span class="hl-1">: </span><span class="hl-3">`comment-</span><span class="hl-4">${</span><span class="hl-2">externalComment</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-7">// What the external system NOW STORES — must match what your sync-in</span><br/><span class="hl-1"> </span><span class="hl-7">// path emits as NewNote.content on re-ingest. The runtime hashes this</span><br/><span class="hl-1"> </span><span class="hl-7">// so the next sync re-listing unchanged content preserves Plot&#39;s</span><br/><span class="hl-1"> </span><span class="hl-7">// (possibly richer-markdown) version instead of clobbering it.</span><br/><span class="hl-1"> </span><span class="hl-15">externalContent</span><span class="hl-1">: </span><span class="hl-2">externalComment</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">,</span><br/><span class="hl-1"> };</span><br/><span class="hl-1">}</span><br/><br/><span class="hl-2">async</span><span class="hl-1"> </span><span class="hl-6">onWebhook</span><span class="hl-1">(</span><span class="hl-2">payload</span><span class="hl-1">: </span><span class="hl-2">WebhookPayload</span><span class="hl-1">): </span><span class="hl-5">Promise</span><span class="hl-1">&lt;</span><span class="hl-4">void</span><span class="hl-1">&gt; {</span><br/><span class="hl-1"> const </span><span class="hl-2">comment</span><span class="hl-1"> = </span><span class="hl-2">payload</span><span class="hl-1">.</span><span class="hl-2">comment</span><span class="hl-1">;</span><br/><br/><span class="hl-1"> </span><span class="hl-7">// Use the Plot ID from metadata if present (handles the race where the</span><br/><span class="hl-1"> </span><span class="hl-7">// webhook arrives before onNoteCreated&#39;s return has been applied),</span><br/><span class="hl-1"> </span><span class="hl-7">// otherwise fall back to upserting by note key alone.</span><br/><span class="hl-1"> await this.tools.integrations.saveLink({</span><br/><span class="hl-1"> </span><span class="hl-15">source</span><span class="hl-1">: </span><span class="hl-2">payload</span><span class="hl-1">.</span><span class="hl-2">itemSource</span><span class="hl-1">, </span><span class="hl-7">// upserts the existing thread+link</span><br/><span class="hl-1"> </span><span class="hl-15">notes</span><span class="hl-1">: [</span><br/><span class="hl-1"> {</span><br/><span class="hl-1"> ...(</span><span class="hl-2">comment</span><span class="hl-1">.</span><span class="hl-2">metadata</span><span class="hl-1">?.</span><span class="hl-2">plotNoteId</span><br/><span class="hl-1"> ? { </span><span class="hl-2">id:</span><span class="hl-1"> </span><span class="hl-2">comment</span><span class="hl-1">.</span><span class="hl-2">metadata</span><span class="hl-1">.</span><span class="hl-2">plotNoteId</span><span class="hl-1"> }</span><br/><span class="hl-1"> : {}),</span><br/><span class="hl-1"> </span><span class="hl-2">key:</span><span class="hl-1"> </span><span class="hl-3">`comment-</span><span class="hl-4">${</span><span class="hl-2">comment</span><span class="hl-13">.</span><span class="hl-2">id</span><span class="hl-4">}</span><span class="hl-3">`</span><span class="hl-1">,</span><br/><span class="hl-1"> </span><span class="hl-2">content:</span><span class="hl-1"> </span><span class="hl-2">comment</span><span class="hl-1">.</span><span class="hl-2">body</span><span class="hl-1">,</span><br/><span class="hl-1"> },</span><br/><span class="hl-1"> ],</span><br/><span class="hl-1"> });</span><br/><span class="hl-1">}</span>
357
+ </code><button type="button">Copy</button></pre>
358
+
359
+ <p>For twists that write notes outside the <code>onNoteCreated</code> dispatch path (explicit <code>pushNoteAsComment</code>-style methods), set <code>key</code> via <code>updateNote</code> after the external write. In that path the sync baseline is <strong>not</strong> established, so the next sync-in will overwrite Plot's content with the external version. Prefer the connector <code>onNoteCreated</code> flow when round-trip preservation matters.</p>
360
+ <p>See <code>connectors/AGENTS.md</code> → &quot;Sync baseline preservation&quot; for the full contract on what <code>externalContent</code> must equal.</p>
361
+ <h2 id="summary" class="tsd-anchor-link">Summary<a href="#summary" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h2><ul>
362
+ <li><strong>Strategy 1</strong> (Create Once): Simplest, no deduplication, use for one-time items</li>
363
+ <li><strong>Strategy 2</strong> (Upsert via Source/Key): Recommended for most integrations, automatic deduplication</li>
364
+ <li><strong>Strategy 3</strong> (Generate and Store IDs): Advanced use cases, maximum flexibility, more complexity</li>
365
+ </ul>
366
+ <p>Start with Strategy 2 for most integrations. Only use Strategy 3 when you have specific requirements that Strategy 2 cannot fulfill.</p>
367
+ <p>For more information:</p>
368
+ <ul>
369
+ <li><a href="Core_Concepts.html">Core Concepts</a> - Understanding threads, notes, and focuses</li>
370
+ <li><a href="Built-in_Tools.html">Tools Guide</a> - Complete reference for the Plot tool</li>
371
+ <li><a href="Building_Connectors.html">Building Connectors</a> - Creating external service integrations</li>
372
+ </ul>
373
+ </div></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>On This Page</h3></summary><div class="tsd-accordion-details"><a href="#sync-strategies"><span>Sync <wbr/>Strategies</span></a><ul><li><a href="#table-of-contents"><span>Table of <wbr/>Contents</span></a></li><li><a href="#overview"><span>Overview</span></a></li><li><a href="#strategy-1-create-once-fire-and-forget"><span>Strategy 1: <wbr/>Create <wbr/>Once (<wbr/>Fire and <wbr/>Forget)</span></a></li><li><ul><li><a href="#when-to-use"><span>When to <wbr/>Use</span></a></li><li><a href="#how-it-works"><span>How <wbr/>It <wbr/>Works</span></a></li><li><a href="#example-simple-notification"><span>Example: <wbr/>Simple <wbr/>Notification</span></a></li><li><a href="#pros-and-cons"><span>Pros and <wbr/>Cons</span></a></li></ul></li><li><a href="#strategy-2-upsert-via-source-and-key-recommended"><span>Strategy 2: <wbr/>Upsert via <wbr/>Source and <wbr/>Key (<wbr/>Recommended)</span></a></li><li><ul><li><a href="#when-to-use-1"><span>When to <wbr/>Use</span></a></li><li><a href="#how-it-works-1"><span>How <wbr/>It <wbr/>Works</span></a></li><li><a href="#link-upserts"><span>Link <wbr/>Upserts</span></a></li><li><a href="#example-calendar-event-sync"><span>Example: <wbr/>Calendar <wbr/>Event <wbr/>Sync</span></a></li><li><a href="#example-taskissue-sync"><span>Example: <wbr/>Task/<wbr/>Issue <wbr/>Sync</span></a></li><li><a href="#referencing-threads-when-creating-notes"><span>Referencing <wbr/>Threads <wbr/>When <wbr/>Creating <wbr/>Notes</span></a></li><li><a href="#note-key-patterns"><span>Note <wbr/>Key <wbr/>Patterns</span></a></li><li><a href="#pros-and-cons-1"><span>Pros and <wbr/>Cons</span></a></li></ul></li><li><a href="#strategy-3-generate-and-store-ids-advanced"><span>Strategy 3: <wbr/>Generate and <wbr/>Store <wbr/>I<wbr/>Ds (<wbr/>Advanced)</span></a></li><li><ul><li><a href="#when-to-use-2"><span>When to <wbr/>Use</span></a></li><li><a href="#how-it-works-2"><span>How <wbr/>It <wbr/>Works</span></a></li><li><a href="#example-multiple-threads-from-single-source"><span>Example: <wbr/>Multiple <wbr/>Threads from <wbr/>Single <wbr/>Source</span></a></li><li><a href="#storage-patterns"><span>Storage <wbr/>Patterns</span></a></li><li><a href="#lookup-and-update-pattern"><span>Lookup and <wbr/>Update <wbr/>Pattern</span></a></li><li><a href="#pros-and-cons-2"><span>Pros and <wbr/>Cons</span></a></li></ul></li><li><a href="#tags-and-reactions"><span>Tags and <wbr/>Reactions</span></a></li><li><ul><li><a href="#syncing-reactions"><span>Syncing <wbr/>Reactions</span></a></li><li><a href="#updating-tags-from-a-twist"><span>Updating <wbr/>Tags from a <wbr/>Twist</span></a></li><li><a href="#completion-state"><span>Completion <wbr/>State</span></a></li></ul></li><li><a href="#initial-vs-incremental-sync"><span>Initial vs <wbr/>Incremental <wbr/>Sync</span></a></li><li><ul><li><a href="#the-flag-pattern"><span>The <wbr/>Flag <wbr/>Pattern</span></a></li><li><a href="#example-implementation"><span>Example <wbr/>Implementation</span></a></li><li><a href="#why-this-matters"><span>Why <wbr/>This <wbr/>Matters</span></a></li><li><a href="#tracking-sync-state"><span>Tracking <wbr/>Sync <wbr/>State</span></a></li></ul></li><li><a href="#choosing-the-right-strategy"><span>Choosing the <wbr/>Right <wbr/>Strategy</span></a></li><li><ul><li><a href="#common-use-cases"><span>Common <wbr/>Use <wbr/>Cases</span></a></li><li><a href="#migration-between-strategies"><span>Migration <wbr/>Between <wbr/>Strategies</span></a></li></ul></li><li><a href="#best-practices"><span>Best <wbr/>Practices</span></a></li><li><ul><li><a href="#1-be-consistent-within-a-connector"><span>1. <wbr/>Be <wbr/>Consistent <wbr/>Within a <wbr/>Connector</span></a></li><li><a href="#2-use-descriptive-keys"><span>2. <wbr/>Use <wbr/>Descriptive <wbr/>Keys</span></a></li><li><a href="#3-handle-missing-sources-gracefully"><span>3. <wbr/>Handle <wbr/>Missing <wbr/>Sources <wbr/>Gracefully</span></a></li><li><a href="#4-document-your-strategy"><span>4. <wbr/>Document <wbr/>Your <wbr/>Strategy</span></a></li><li><a href="#5-clean-up-when-needed"><span>5. <wbr/>Clean <wbr/>Up <wbr/>When <wbr/>Needed</span></a></li><li><a href="#6-avoid-race-conditions-in-two-way-sync"><span>6. <wbr/>Avoid <wbr/>Race <wbr/>Conditions in <wbr/>Two-<wbr/>Way <wbr/>Sync</span></a></li></ul></li><li><a href="#summary"><span>Summary</span></a></li></ul></div></details></div><div class="site-menu"><nav id="tsd-sidebar-links" class="tsd-navigation"><a href="https://plot.day" class="tsd-nav-link">Plot</a><a href="https://github.com/plotday/plot" class="tsd-nav-link">GitHub</a><a href="https://www.npmjs.com/package/@plotday/twister" class="tsd-nav-link">NPM</a></nav><nav class="tsd-navigation"><a href="../modules.html">Creating Plot Twists</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>