/** * Copyright (c) 2026, Salesforce, Inc., * All rights reserved. * For full license text, see the LICENSE.txt file */ import { getOrgAuth } from "../lib/auth.js"; import { getSchemaMetadata } from "../lib/introspect.js"; import { type PrimeDeps, primeSchemaWithLock } from "../lib/prime-schema.js"; export async function connectCommand( orgAlias: string, opts: { refresh?: boolean } = {}, deps?: PrimeDeps, ): Promise { const getAuth = deps?.getOrgAuth ?? getOrgAuth; console.log(`Connecting to ${orgAlias}...`); const auth = await getAuth(orgAlias); console.log(`Authenticated as ${auth.username} on ${auth.instanceUrl}`); if (!opts.refresh) { // Resolve via the already-resolved instance URL (not the alias) so we // don't trigger a second org resolution, and so injected deps stay honored. const existing = getSchemaMetadata(auth.instanceUrl); if (existing) { const age = formatSchemaAge(new Date(existing.downloadedAt)); console.log(`Schema already cached: ${existing.typeCount} types (downloaded ${age}).`); console.log(`Run \`graphiti connect ${orgAlias} --refresh\` to re-download.`); return; } } const verb = opts.refresh ? "Refreshing" : "Downloading"; console.log(`${verb} GraphQL schema via introspection...`); const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; let i = 0; const startTime = Date.now(); const spinner = setInterval(() => { const elapsed = ((Date.now() - startTime) / 1000).toFixed(0); process.stderr.write(`\r${frames[i++ % frames.length]} ${verb} schema... (${elapsed}s)`); }, 100); try { // Route through the shared priming layer so a refresh coherently clears // all three caches (introspection JSON, in-memory schema, ObjectInfo) and // coalesces with any concurrent CLI/MCP refresh. A failed refresh throws // SchemaRefreshError (kept caches + staleness message), surfaced by cli.ts. const result = await primeSchemaWithLock(orgAlias, deps, { forceRefresh: !!opts.refresh }); clearInterval(spinner); process.stderr.write("\r"); const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); // `result.typeCount` is set on the download path; a coalesced refresh // (no download performed here) falls back to a one-time metadata read. const typeCount = result.typeCount ?? getSchemaMetadata(auth.instanceUrl)?.typeCount ?? 0; if (opts.refresh && !result.refreshed) { // A concurrent refresh produced the new schema while we waited. console.log( `Schema for ${orgAlias} was just refreshed by a concurrent process: ${typeCount} types.`, ); } else if (opts.refresh) { console.log(`Refreshed ${orgAlias}. Schema cached: ${typeCount} types (${elapsed}s).`); } else { console.log(`Connected to ${orgAlias}. Schema cached: ${typeCount} types (${elapsed}s).`); } } catch (err) { clearInterval(spinner); process.stderr.write("\r"); throw err; } } function formatSchemaAge(date: Date): string { const diffMs = Date.now() - date.getTime(); const diffMin = Math.floor(diffMs / 60_000); if (diffMin < 1) return "just now"; if (diffMin < 60) return `${diffMin}m ago`; const diffHr = Math.floor(diffMin / 60); if (diffHr < 24) return `${diffHr}h ago`; const diffDays = Math.floor(diffHr / 24); return `${diffDays}d ago`; }