thinkwork-cli 0.13.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/README.md +3 -14
  2. package/dist/{api-client-4DZ266J5.js → api-client-4P4JZOBX.js} +3 -1
  3. package/dist/bowser-6Z6JFAIR.js +2822 -0
  4. package/dist/{chunk-Y2WHJECR.js → chunk-4OMDWYZQ.js} +5 -44
  5. package/dist/chunk-AD2NIWB5.js +6516 -0
  6. package/dist/chunk-DV3MRBR5.js +43 -0
  7. package/dist/chunk-EI5ZX7CN.js +522 -0
  8. package/dist/chunk-EUJ7X6YD.js +126 -0
  9. package/dist/chunk-GG5WKBOX.js +436 -0
  10. package/dist/chunk-I64EZSU5.js +358 -0
  11. package/dist/chunk-KMIJ5KGT.js +128 -0
  12. package/dist/chunk-MWQ7RO76.js +137 -0
  13. package/dist/chunk-OCPHU34C.js +996 -0
  14. package/dist/chunk-OLUQ4U2Z.js +1728 -0
  15. package/dist/chunk-XYM7PE2A.js +2264 -0
  16. package/dist/chunk-YQCYD6NE.js +3299 -0
  17. package/dist/cli.js +49156 -31257
  18. package/dist/commands/enterprise/templates/deploy-repo/scripts/apply-release.mjs +0 -1
  19. package/dist/commands/enterprise/templates/deploy-repo/scripts/smoke.mjs +0 -1
  20. package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +0 -14
  21. package/dist/dist-es-2OTNTL4N.js +315 -0
  22. package/dist/dist-es-2XVAH4BQ.js +14726 -0
  23. package/dist/dist-es-3DPDUJAH.js +2000 -0
  24. package/dist/dist-es-BNZTBBVC.js +176 -0
  25. package/dist/dist-es-CD2RBDAC.js +88 -0
  26. package/dist/dist-es-I4FW57PS.js +88 -0
  27. package/dist/dist-es-KL7Y5NB2.js +377 -0
  28. package/dist/dist-es-KVFWJNDY.js +318 -0
  29. package/dist/dist-es-PS73DWNL.js +70 -0
  30. package/dist/dist-es-S3NZQFGT.js +170 -0
  31. package/dist/dist-es-SGCA4P32.js +48 -0
  32. package/dist/dist-es-WBM6J4RA.js +70 -0
  33. package/dist/dist-es-ZGVADYIF.js +485 -0
  34. package/dist/dist-es-ZKC2QKD2.js +377 -0
  35. package/dist/drizzle/0076_scheduled_jobs_marco_backfill.sql +2 -1
  36. package/dist/drizzle/0079_seed_tenant_customize_catalog.sql +1 -0
  37. package/dist/drizzle/0090_brain_schema_extraction.sql +10 -0
  38. package/dist/drizzle/0132_drop_computer_tables.sql +3 -0
  39. package/dist/drizzle/0160_compliance_event_types_plugins.sql +2 -0
  40. package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +1 -0
  41. package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +1 -0
  42. package/dist/drizzle/0227_analyst_reader_role.sql +17 -20
  43. package/dist/drizzle/0230_analyst_rls.sql +28 -36
  44. package/dist/drizzle/0250_kg_schema_extraction.sql +27 -0
  45. package/dist/drizzle/0251_brain_stragglers.sql +21 -0
  46. package/dist/drizzle/0253_drop_brain_substrate.sql +2 -0
  47. package/dist/drizzle/0257_agents_runtime_allow_harness.sql +24 -0
  48. package/dist/drizzle/0261_native_auth_control_plane.sql +332 -0
  49. package/dist/drizzle/0262_auth_subscription_tickets.sql +59 -0
  50. package/dist/drizzle/0263_agentcore_runtime_identifier.sql +69 -0
  51. package/dist/drizzle/0263_drop_workos_auth_runtime.sql +236 -0
  52. package/dist/drizzle/0264_auth_enrollment_attempt_limits.sql +15 -0
  53. package/dist/drizzle/0265_eval_runs_requester_identity.sql +28 -0
  54. package/dist/drizzle/0265_require_fully_valid_auth_publication.sql +27 -0
  55. package/dist/drizzle/0266_auth_identity_recovery_grants.sql +9 -0
  56. package/dist/drizzle/0266_eval_profiles_runtime_type.sql +12 -0
  57. package/dist/drizzle/0267_ontology_candidate_rejections.sql +53 -0
  58. package/dist/drizzle/0268_identity_crosswalk_routing.sql +182 -0
  59. package/dist/drizzle/0269_ontology_system_map.sql +31 -0
  60. package/dist/drizzle/0270_tool_execution_ledger.sql +115 -0
  61. package/dist/drizzle/0271_ontology_twin_declarations.sql +47 -0
  62. package/dist/drizzle/0272_identity_graph_projection_cursor.sql +23 -0
  63. package/dist/drizzle/0273_twin_materialization_suggestions.sql +29 -0
  64. package/dist/drizzle/0274_tenant_mcp_twin_keys.sql +59 -0
  65. package/dist/drizzle/0275_identity_bulk_rebuild_fence.sql +30 -0
  66. package/dist/drizzle/0276_tool_execution_ledger_allow_cascade_delete.sql +18 -0
  67. package/dist/drizzle/0277_knowledge_base_sources.sql +107 -0
  68. package/dist/drizzle/0278_kb_page_transcription.sql +55 -0
  69. package/dist/drizzle/0279_mcp_servers_off_plugin_provenance.sql +93 -0
  70. package/dist/drizzle/0280_work_item_external_refs_drop_plane_provider.sql +46 -0
  71. package/dist/drizzle/0281_tenant_mcp_twin_keys_expiry_suffix.sql +9 -0
  72. package/dist/drizzle/0282_tenant_mcp_twin_keys_grants.sql +24 -0
  73. package/dist/drizzle/0283_agents_agentcore_runtime_dispatch.sql +13 -0
  74. package/dist/drizzle/0284_user_brain_claims.sql +69 -0
  75. package/dist/drizzle/0285_tenant_mcp_twin_keys_trusted_subsystem.sql +30 -0
  76. package/dist/drizzle/0286_brain_analytics_key.sql +26 -0
  77. package/dist/drizzle/0287_brain_subject.sql +25 -0
  78. package/dist/drizzle/0288_multi_lane_identities.sql +25 -0
  79. package/dist/event-streams-7JNLJEEL.js +894 -0
  80. package/dist/event-streams-KDF5RWR6.js +1515 -0
  81. package/dist/loadSso-ETCXTR4I.js +576 -0
  82. package/dist/loadSso-UBD7KUHE.js +583 -0
  83. package/dist/plugins/README.md +2 -2
  84. package/dist/plugins/catalog/package.json +1 -5
  85. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +6 -4
  86. package/dist/plugins/catalog/src/__tests__/catalog.test.ts +21 -21
  87. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +102 -94
  88. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +7 -8
  89. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -20
  90. package/dist/plugins/catalog/src/contracts.ts +17 -179
  91. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -24
  92. package/dist/plugins/n8n/test/integrated-app-contract.test.ts +1 -1
  93. package/dist/plugins/twenty/package.json +2 -1
  94. package/dist/plugins/twenty/scripts/LAMBDA_DEPLOY.md +114 -0
  95. package/dist/plugins/twenty/scripts/build-lambda.sh +29 -0
  96. package/dist/plugins/twenty/scripts/configure-product-views.ts +195 -0
  97. package/dist/plugins/twenty/scripts/lambda-handler.ts +49 -0
  98. package/dist/plugins/twenty/scripts/lib/__tests__/load-attachments.test.ts +38 -0
  99. package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +145 -2
  100. package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +397 -151
  101. package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +28 -129
  102. package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +10 -0
  103. package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +13 -8
  104. package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +222 -2
  105. package/dist/plugins/twenty/scripts/lib/load-attachments.ts +29 -0
  106. package/dist/plugins/twenty/scripts/lib/load-records.ts +160 -43
  107. package/dist/plugins/twenty/scripts/lib/mappers.ts +368 -152
  108. package/dist/plugins/twenty/scripts/lib/members-ensure.ts +43 -268
  109. package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +57 -0
  110. package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +136 -31
  111. package/dist/plugins/twenty/scripts/migrate-lastmile.ts +254 -152
  112. package/dist/plugins/twenty/scripts/provision-twenty-members.ts +44 -1
  113. package/dist/plugins/twenty/scripts/purge-lastmile-import.ts +178 -0
  114. package/dist/plugins/twenty/scripts/restructure-products.ts +312 -0
  115. package/dist/plugins/twenty/scripts/retag-stage-values.ts +362 -0
  116. package/dist/plugins/twenty/test/manifest.test.ts +2 -4
  117. package/dist/scripts/post-deploy.sh +79 -1
  118. package/dist/signin-5QZNRSOO.js +821 -0
  119. package/dist/sso-oidc-XK673P54.js +837 -0
  120. package/dist/sts-OXI2UALF.js +1113 -0
  121. package/dist/sts-SQW4NBK3.js +1117 -0
  122. package/dist/terraform/examples/greenfield/main.tf +391 -176
  123. package/dist/terraform/examples/greenfield/terraform.tfvars.example +26 -8
  124. package/dist/terraform/modules/app/agentcore-admin/main.tf +47 -0
  125. package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +39 -47
  126. package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +13 -66
  127. package/dist/terraform/modules/app/agentcore-identity/main.tf +253 -0
  128. package/dist/terraform/modules/app/agentcore-identity/outputs.tf +51 -0
  129. package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh +239 -0
  130. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_identity.sh +22 -0
  131. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_twenty_identity.sh +16 -0
  132. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_workload_identity.sh +19 -0
  133. package/dist/terraform/modules/app/agentcore-identity/scripts/ensure_workload_identity.sh +29 -0
  134. package/dist/terraform/modules/app/agentcore-identity/scripts/read_identity.sh +55 -0
  135. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_identity.sh +73 -0
  136. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_identity.sh +45 -0
  137. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_provider.mjs +123 -0
  138. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/after.tf +15 -0
  139. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/before.tf +7 -0
  140. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/identity-after.tf +8 -0
  141. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/identity-before.tf +9 -0
  142. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/thinkwork-after.tf +3 -0
  143. package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/thinkwork-before.tf +3 -0
  144. package/dist/terraform/modules/app/agentcore-identity/variables.tf +60 -0
  145. package/dist/terraform/modules/app/agentcore-memory/README.md +46 -9
  146. package/dist/terraform/modules/app/agentcore-memory/main.tf +27 -10
  147. package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +156 -24
  148. package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
  149. package/dist/terraform/modules/app/agentcore-pi/main.tf +42 -52
  150. package/dist/terraform/modules/app/agentcore-pi/outputs.tf +3 -3
  151. package/dist/terraform/modules/app/agentcore-pi/scripts/reconcile_pi_runtime.mjs +175 -0
  152. package/dist/terraform/modules/app/agentcore-pi/variables.tf +35 -28
  153. package/dist/terraform/modules/app/agentcore-platform/main.tf +68 -0
  154. package/dist/terraform/modules/app/agentcore-runtime/main.tf +4 -20
  155. package/dist/terraform/modules/app/appsync-subscriptions/main.tf +127 -21
  156. package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +0 -6
  157. package/dist/terraform/modules/app/appsync-subscriptions/variables.tf +5 -0
  158. package/dist/terraform/modules/app/capability-broker/main.tf +2 -2
  159. package/dist/terraform/modules/app/capability-broker/variables.tf +11 -0
  160. package/dist/terraform/modules/app/deployment-control-plane/README.md +264 -0
  161. package/dist/terraform/modules/app/deployment-control-plane/buildspec.yml +4 -1
  162. package/dist/terraform/modules/app/deployment-control-plane/main.tf +94 -0
  163. package/dist/terraform/modules/app/deployment-control-plane/outputs.tf +5 -0
  164. package/dist/terraform/modules/app/deployment-control-plane/runner.py +3689 -233
  165. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +2579 -140
  166. package/dist/terraform/modules/app/deployment-control-plane/variables.tf +33 -0
  167. package/dist/terraform/modules/app/lambda-api/auth-state.tf +72 -0
  168. package/dist/terraform/modules/app/lambda-api/chat-latency-observability.tf +466 -0
  169. package/dist/terraform/modules/app/lambda-api/dispatch-dlq.tf +55 -0
  170. package/dist/terraform/modules/app/lambda-api/handlers.tf +531 -619
  171. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +350 -241
  172. package/dist/terraform/modules/app/lambda-api/main.tf +75 -13
  173. package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +3 -28
  174. package/dist/terraform/modules/app/lambda-api/oauth-secrets.tf +1 -2
  175. package/dist/terraform/modules/app/lambda-api/outputs.tf +17 -10
  176. package/dist/terraform/modules/app/lambda-api/runtime-config.tf +3 -21
  177. package/dist/terraform/modules/app/lambda-api/turn-assertion.tf +45 -0
  178. package/dist/terraform/modules/app/lambda-api/variables.tf +307 -121
  179. package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/README.md +1 -1
  180. package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/main.tf +18 -18
  181. package/dist/terraform/modules/app/sandbox-log-scrubber/README.md +11 -10
  182. package/dist/terraform/modules/app/sandbox-log-scrubber/main.tf +5 -5
  183. package/dist/terraform/modules/app/static-site/main.tf +1 -1
  184. package/dist/terraform/modules/app/www-dns/main.tf +9 -27
  185. package/dist/terraform/modules/app/www-dns/variables.tf +4 -10
  186. package/dist/terraform/modules/data/aurora-postgres/README.md +34 -0
  187. package/dist/terraform/modules/data/aurora-postgres/alarms.tf +82 -0
  188. package/dist/terraform/modules/data/aurora-postgres/main.tf +27 -57
  189. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +0 -9
  190. package/dist/terraform/modules/data/aurora-postgres/variables.tf +41 -0
  191. package/dist/terraform/modules/data/s3-buckets/main.tf +5 -6
  192. package/dist/terraform/modules/foundation/cognito/main.tf +367 -72
  193. package/dist/terraform/modules/foundation/cognito/outputs.tf +47 -0
  194. package/dist/terraform/modules/foundation/cognito/variables.tf +131 -9
  195. package/dist/terraform/modules/thinkwork/README.md +5 -1
  196. package/dist/terraform/modules/thinkwork/main.tf +459 -234
  197. package/dist/terraform/modules/thinkwork/outputs.tf +44 -62
  198. package/dist/terraform/modules/thinkwork/variables.tf +353 -89
  199. package/dist/terraform/schema.graphql +69 -36
  200. package/dist/workspace-defaults/files/INSTRUCTIONS.md +9 -11
  201. package/dist/workspace-defaults/files/MEMORY_GUIDE.md +38 -35
  202. package/dist/workspace-defaults/files/ROUTER.md +1 -1
  203. package/dist/workspace-defaults/files/TOOLS.md +9 -39
  204. package/dist/workspace-defaults/files/USER.md +1 -0
  205. package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +6 -3
  206. package/dist/workspace-defaults/files/catalog-skills/brain-brief-builder/SKILL.md +165 -0
  207. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/SKILL.md +75 -0
  208. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/mcp-tooling.md +40 -0
  209. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/validation-and-handoff.md +40 -0
  210. package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/workflow-authoring.md +46 -0
  211. package/dist/workspace-defaults/files/skills/artifact-builder/references/crm-dashboard.md +1 -1
  212. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +22 -3
  213. package/package.json +3 -2
  214. package/dist/drizzle/0261_thread_turns_origin_turn_index.sql +0 -11
  215. package/dist/plugins/company-data/README.md +0 -49
  216. package/dist/plugins/company-data/package.json +0 -21
  217. package/dist/plugins/company-data/src/index.ts +0 -30
  218. package/dist/plugins/company-data/src/manifest.ts +0 -26
  219. package/dist/plugins/company-data/test/manifest.test.ts +0 -116
  220. package/dist/plugins/company-data/tsconfig.json +0 -8
  221. package/dist/plugins/company-etl/README.md +0 -46
  222. package/dist/plugins/company-etl/package.json +0 -21
  223. package/dist/plugins/company-etl/src/index.ts +0 -29
  224. package/dist/plugins/company-etl/src/manifest.ts +0 -26
  225. package/dist/plugins/company-etl/test/manifest.test.ts +0 -108
  226. package/dist/plugins/company-etl/tsconfig.json +0 -8
  227. package/dist/plugins/lastmile/README.md +0 -50
  228. package/dist/plugins/lastmile/package.json +0 -23
  229. package/dist/plugins/lastmile/smoke/lastmile-plugin-smoke.mjs +0 -661
  230. package/dist/plugins/lastmile/src/api/tasks-adapter.ts +0 -572
  231. package/dist/plugins/lastmile/src/discovery.fixture.ts +0 -64
  232. package/dist/plugins/lastmile/src/index.ts +0 -46
  233. package/dist/plugins/lastmile/src/manifest.ts +0 -139
  234. package/dist/plugins/lastmile/test/api/tasks-adapter.test.ts +0 -266
  235. package/dist/plugins/lastmile/test/discovery.test.ts +0 -88
  236. package/dist/plugins/lastmile/tsconfig.json +0 -8
  237. package/dist/plugins/n8n/smoke/n8n-managed-app-smoke.mjs +0 -409
  238. package/dist/plugins/twenty/smoke/twenty-managed-app-smoke.mjs +0 -359
  239. package/dist/plugins/workos-auth/package.json +0 -22
  240. package/dist/plugins/workos-auth/src/index.ts +0 -30
  241. package/dist/plugins/workos-auth/src/manifest.ts +0 -40
  242. package/dist/plugins/workos-auth/src/provider-contract.ts +0 -39
  243. package/dist/plugins/workos-auth/test/manifest.test.ts +0 -94
  244. package/dist/plugins/workos-auth/tsconfig.json +0 -8
  245. package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +0 -61
  246. package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/sitecustomize.py +0 -227
  247. package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/test_sitecustomize.py +0 -277
  248. package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh +0 -70
  249. package/dist/terraform/modules/app/agentcore-harness/main.tf +0 -101
  250. package/dist/terraform/modules/app/agentcore-harness/outputs.tf +0 -9
  251. package/dist/terraform/modules/app/agentcore-harness/variables.tf +0 -25
  252. package/dist/terraform/modules/app/hindsight-memory/README.md +0 -89
  253. package/dist/terraform/modules/app/hindsight-memory/main.tf +0 -411
  254. package/dist/terraform/modules/data/bedrock-knowledge-base/main.tf +0 -102
  255. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/outputs.tf +0 -0
  256. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/scripts/sync-database.py +0 -0
  257. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/scripts/test_sync_database.py +0 -0
  258. /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/variables.tf +0 -0
  259. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/README.md +0 -0
  260. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/main.tf +0 -0
  261. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/outputs.tf +0 -0
  262. /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/variables.tf +0 -0
@@ -24,15 +24,24 @@ import {
24
24
 
25
25
  const QUERY_PAGE = 200;
26
26
 
27
- /** True when Twenty rejects a filter because the sourceId custom field does
28
- * not exist yet (virgin workspace before schema-ensure applies the dry-run
29
- * plans the field but cannot create it). No field no record carries it. */
27
+ /**
28
+ * True when Twenty rejects a query because schema this run would create does
29
+ * not exist yet: a missing `sourceId` custom field, or a whole missing custom
30
+ * object (`opportunityProduct`, `organization`). A dry-run plans that schema
31
+ * but cannot create it, so nothing can carry a sourceId and the honest answer
32
+ * to "what already exists?" is "nothing".
33
+ *
34
+ * Apply runs create objects and fields before any record pass, so this can
35
+ * only fire in dry-run; a genuine apply-time schema gap aborts in the metadata
36
+ * phase, before record loading.
37
+ */
30
38
  export function isMissingSourceIdFieldError(error: unknown): boolean {
31
- return (
32
- error instanceof TwentyGraphqlError &&
33
- error.errors.some((entry) =>
34
- /doesn't have any .?sourceId.? field/i.test(entry.message),
35
- )
39
+ if (!(error instanceof TwentyGraphqlError)) return false;
40
+ return error.errors.some(
41
+ (entry) =>
42
+ /doesn't have any .?sourceId.? field/i.test(entry.message) ||
43
+ /Unknown type "\w+FilterInput"/i.test(entry.message) ||
44
+ /Cannot query field "\w+" on type "Query"/i.test(entry.message),
36
45
  );
37
46
  }
38
47
 
@@ -71,6 +80,18 @@ export const OPPORTUNITY_PRODUCT: EntityShape = {
71
80
  capSingular: "OpportunityProduct",
72
81
  capPlural: "OpportunityProducts",
73
82
  };
83
+ export const ORGANIZATION: EntityShape = {
84
+ singular: "organization",
85
+ plural: "organizations",
86
+ capSingular: "Organization",
87
+ capPlural: "Organizations",
88
+ };
89
+ export const PRODUCT: EntityShape = {
90
+ singular: "product",
91
+ plural: "products",
92
+ capSingular: "Product",
93
+ capPlural: "Products",
94
+ };
74
95
  export const NOTE: EntityShape = {
75
96
  singular: "note",
76
97
  plural: "notes",
@@ -471,11 +492,19 @@ export async function upsertNotes(options: {
471
492
  return false;
472
493
  });
473
494
 
495
+ // createdAt is replayed from LastMile so the activity feed reads in the order
496
+ // reps actually wrote the notes. Twenty accepts it on create AND update, so
497
+ // notes seeded before this behaviour existed heal on the next run (their
498
+ // content hash changes when createdAt joins it).
474
499
  const mapped: MappedRecord[] = resolvable.map((note) => ({
475
500
  sourceId: note.sourceId,
476
501
  input: {
477
502
  title: note.title,
478
503
  bodyV2: { markdown: note.bodyMarkdown },
504
+ ...(note.createdAt
505
+ ? { createdAt: note.createdAt, updatedAt: note.createdAt }
506
+ : {}),
507
+ ...(note.authorName ? { author: note.authorName } : {}),
479
508
  sourceId: note.sourceId,
480
509
  },
481
510
  warnings: [],
@@ -499,41 +528,106 @@ export async function upsertNotes(options: {
499
528
 
500
529
  // NoteTarget is ensured UNCONDITIONALLY — including for hash-skipped notes —
501
530
  // so a crash between the Note write and the NoteTarget write heals on
502
- // re-run (plan U6).
503
- for (const note of resolvable) {
504
- if (dryRun) continue; // note creates already planned; target ensure follows them
505
- const noteId = noteIdBySourceId.get(note.sourceId);
506
- const targetId = targetIdBySourceId.get(note.targetSourceId);
507
- if (!noteId || !targetId) continue;
508
- try {
509
- const existing = await client.requestWithRetry<{
510
- noteTargets: { edges: Array<{ node: { id: string } }> };
511
- }>(
512
- "/graphql",
513
- `query MigrationNoteTargets($filter: NoteTargetFilterInput) {
514
- noteTargets(filter: $filter, first: 1) { edges { node { id } } }
515
- }`,
516
- { filter: { noteId: { eq: noteId } } },
531
+ // re-run (plan U6). The existence check is BATCHED: one paginated
532
+ // `noteTargets` query per QUERY_PAGE notes rather than one query per note.
533
+ // The per-note form dominated a steady-state run every already-paired note
534
+ // (thousands of them) cost a round-trip at the API rate limit, ~20 min of a
535
+ // 26 min run. Batched, the same phase is a handful of queries.
536
+ if (!dryRun) {
537
+ const pairs = resolvable
538
+ .map((note) => ({
539
+ note,
540
+ noteId: noteIdBySourceId.get(note.sourceId),
541
+ targetId: targetIdBySourceId.get(note.targetSourceId),
542
+ }))
543
+ .filter(
544
+ (p): p is { note: MappedNote; noteId: string; targetId: string } =>
545
+ Boolean(p.noteId) && Boolean(p.targetId),
517
546
  );
518
- if (existing.noteTargets.edges.length > 0) continue;
519
- const targetField =
520
- note.targetKind === "company"
547
+
548
+ // Which noteIds already have a target? Paginate within each chunk so a
549
+ // multi-target note can never be truncated into a false "unpaired".
550
+ type NoteTargetPage = {
551
+ noteTargets: {
552
+ edges: Array<{ node: { noteId: string } }>;
553
+ pageInfo: { hasNextPage: boolean; endCursor: string | null };
554
+ };
555
+ };
556
+ const alreadyPaired = new Set<string>();
557
+ const noteIds = [...new Set(pairs.map((p) => p.noteId))];
558
+ for (const page of chunk(noteIds, QUERY_PAGE)) {
559
+ let after: string | null = null;
560
+ try {
561
+ do {
562
+ const data: NoteTargetPage =
563
+ await client.requestWithRetry<NoteTargetPage>(
564
+ "/graphql",
565
+ `query MigrationNoteTargets($filter: NoteTargetFilterInput, $after: String) {
566
+ noteTargets(filter: $filter, first: ${QUERY_PAGE}, after: $after) {
567
+ edges { node { noteId } }
568
+ pageInfo { hasNextPage endCursor }
569
+ }
570
+ }`,
571
+ { filter: { noteId: { in: [...page] } }, after },
572
+ );
573
+ for (const { node } of data.noteTargets.edges)
574
+ alreadyPaired.add(node.noteId);
575
+ after = data.noteTargets.pageInfo.hasNextPage
576
+ ? data.noteTargets.pageInfo.endCursor
577
+ : null;
578
+ } while (after);
579
+ } catch (error) {
580
+ counters.failed += page.length;
581
+ counters.gaps.push(
582
+ `noteTarget lookup batch failed: ${
583
+ error instanceof Error ? error.message.slice(0, 300) : String(error)
584
+ }`,
585
+ );
586
+ }
587
+ }
588
+
589
+ // Create the missing targets, grouped by FK field, in BATCH_LIMIT chunks
590
+ // (createNoteTargets takes an array).
591
+ const toCreate = new Map<
592
+ string,
593
+ Array<{ noteId: string; targetId: string }>
594
+ >();
595
+ for (const p of pairs) {
596
+ if (alreadyPaired.has(p.noteId)) continue;
597
+ const field =
598
+ p.note.targetKind === "company"
521
599
  ? "targetCompanyId"
522
600
  : "targetOpportunityId";
523
- await client.requestOnce(
524
- "/graphql",
525
- `mutation MigrationCreateNoteTargets($data: [NoteTargetCreateInput!]!) {
526
- createNoteTargets(data: $data) { id }
527
- }`,
528
- { data: [{ noteId, [targetField]: targetId }] },
529
- );
530
- } catch (error) {
531
- counters.failed += 1;
532
- counters.gaps.push(
533
- `noteTarget for ${note.sourceId} failed: ${
534
- error instanceof Error ? error.message.slice(0, 300) : String(error)
535
- }`,
536
- );
601
+ const list = toCreate.get(field) ?? [];
602
+ list.push({ noteId: p.noteId, targetId: p.targetId });
603
+ toCreate.set(field, list);
604
+ }
605
+ for (const [field, list] of toCreate) {
606
+ for (const batch of chunk(list, BATCH_LIMIT)) {
607
+ try {
608
+ await client.requestOnce(
609
+ "/graphql",
610
+ `mutation MigrationCreateNoteTargets($data: [NoteTargetCreateInput!]!) {
611
+ createNoteTargets(data: $data) { id }
612
+ }`,
613
+ {
614
+ data: batch.map((p) => ({
615
+ noteId: p.noteId,
616
+ [field]: p.targetId,
617
+ })),
618
+ },
619
+ );
620
+ } catch (error) {
621
+ counters.failed += batch.length;
622
+ counters.gaps.push(
623
+ `noteTarget create batch (${field}) failed: ${
624
+ error instanceof Error
625
+ ? error.message.slice(0, 300)
626
+ : String(error)
627
+ }`,
628
+ );
629
+ }
630
+ }
537
631
  }
538
632
  }
539
633
 
@@ -592,11 +686,19 @@ export async function upsertOpportunityProducts(options: {
592
686
  products: MappedOpportunityProduct[];
593
687
  /** opportunity sourceId -> Twenty opportunity id. */
594
688
  opportunityIdBySourceId: ReadonlyMap<string, string>;
689
+ /** catalog product sourceId -> Twenty product id. */
690
+ productIdBySourceId: ReadonlyMap<string, string>;
595
691
  dryRun: boolean;
596
692
  counters: EntityCounters;
597
693
  }): Promise<void> {
598
- const { client, products, opportunityIdBySourceId, dryRun, counters } =
599
- options;
694
+ const {
695
+ client,
696
+ products,
697
+ opportunityIdBySourceId,
698
+ productIdBySourceId,
699
+ dryRun,
700
+ counters,
701
+ } = options;
600
702
 
601
703
  const resolvable: MappedRecord[] = [];
602
704
  for (const product of products) {
@@ -611,9 +713,24 @@ export async function upsertOpportunityProducts(options: {
611
713
  );
612
714
  continue;
613
715
  }
716
+ // A line whose brand did not map to a catalog product still migrates with
717
+ // its quantity and amount; it simply has no product. The warning already
718
+ // rode along from the mapper.
719
+ const productId = product.productSourceId
720
+ ? (productIdBySourceId.get(product.productSourceId) ?? null)
721
+ : null;
722
+ if (product.productSourceId && !productId) {
723
+ counters.gaps.push(
724
+ `line ${product.sourceId}: catalog product ${product.productSourceId} missing`,
725
+ );
726
+ }
614
727
  resolvable.push({
615
728
  sourceId: product.sourceId,
616
- input: { ...product.input, opportunityId },
729
+ input: {
730
+ ...product.input,
731
+ opportunityId,
732
+ ...(productId ? { productId } : {}),
733
+ },
617
734
  warnings: product.warnings,
618
735
  });
619
736
  }