UNPKG

@codama/renderers-js-umi

Version:

JavaScript renderer compatible with the Umi framework

1 lines 157 kB
{"version":3,"sources":["../src/ImportMap.ts","../src/getRenderMapVisitor.ts","../src/ContextMap.ts","../src/getTypeManifestVisitor.ts","../src/utils/codecs.ts","../src/utils/customData.ts","../src/utils/gpaField.ts","../src/utils/linkOverrides.ts","../src/utils/render.ts","../src/renderInstructionDefaults.ts","../src/renderVisitor.ts","../src/getValidatorBagVisitor.ts"],"sourcesContent":["import { TypeManifest } from './getTypeManifestVisitor';\n\nconst DEFAULT_MODULE_MAP: Record<string, string> = {\n errors: '../errors',\n shared: '../shared',\n types: '../types',\n umi: '@metaplex-foundation/umi',\n umiSerializers: '@metaplex-foundation/umi/serializers',\n};\n\nexport class ImportMap {\n protected readonly _imports: Map<string, Set<string>> = new Map();\n\n protected readonly _aliases: Map<string, Record<string, string>> = new Map();\n\n add(module: string, imports: Set<string> | string[] | string): ImportMap {\n const currentImports = this._imports.get(module) ?? new Set();\n const newImports = typeof imports === 'string' ? [imports] : imports;\n newImports.forEach(i => currentImports.add(i));\n this._imports.set(module, currentImports);\n return this;\n }\n\n remove(module: string, imports: Set<string> | string[] | string): ImportMap {\n const currentImports = this._imports.get(module) ?? new Set();\n const importsToRemove = typeof imports === 'string' ? [imports] : imports;\n importsToRemove.forEach(i => currentImports.delete(i));\n if (currentImports.size === 0) {\n this._imports.delete(module);\n } else {\n this._imports.set(module, currentImports);\n }\n return this;\n }\n\n mergeWith(...others: ImportMap[]): ImportMap {\n others.forEach(other => {\n other._imports.forEach((imports, module) => {\n this.add(module, imports);\n });\n other._aliases.forEach((aliases, module) => {\n Object.entries(aliases).forEach(([name, alias]) => {\n this.addAlias(module, name, alias);\n });\n });\n });\n return this;\n }\n\n mergeWithManifest(manifest: TypeManifest): ImportMap {\n return this.mergeWith(manifest.strictImports, manifest.looseImports, manifest.serializerImports);\n }\n\n addAlias(module: string, name: string, alias: string): ImportMap {\n const currentAliases = this._aliases.get(module) ?? {};\n currentAliases[name] = alias;\n this._aliases.set(module, currentAliases);\n return this;\n }\n\n isEmpty(): boolean {\n return this._imports.size === 0;\n }\n\n toString(dependencies: Record<string, string>): string {\n const dependencyMap = { ...DEFAULT_MODULE_MAP, ...dependencies };\n const importStatements = [...this._imports.entries()]\n .map(([module, imports]) => {\n const mappedModule: string = dependencyMap[module] ?? module;\n return [mappedModule, module, imports] as const;\n })\n .sort(([a], [b]) => {\n const aIsRelative = a.startsWith('.');\n const bIsRelative = b.startsWith('.');\n if (aIsRelative && !bIsRelative) return 1;\n if (!aIsRelative && bIsRelative) return -1;\n return a.localeCompare(b);\n })\n .map(([mappedModule, module, imports]) => {\n const aliasMap = this._aliases.get(module) ?? {};\n const joinedImports = [...imports]\n .sort()\n .map(i => (aliasMap[i] ? `${i} as ${aliasMap[i]}` : i))\n .join(', ');\n return `import { ${joinedImports} } from '${mappedModule}';`;\n });\n return importStatements.join('\\n');\n }\n}\n","import { logWarn } from '@codama/errors';\nimport {\n camelCase,\n CamelCaseString,\n definedTypeNode,\n FieldDiscriminatorNode,\n getAllAccounts,\n getAllDefinedTypes,\n getAllInstructionArguments,\n getAllInstructionsWithSubs,\n getAllPrograms,\n InstructionNode,\n isDataEnum,\n isNode,\n isNodeFilter,\n parseOptionalAccountStrategy,\n pascalCase,\n ProgramNode,\n resolveNestedTypeNode,\n SizeDiscriminatorNode,\n structTypeNodeFromInstructionArgumentNodes,\n VALUE_NODES,\n} from '@codama/nodes';\nimport { addToRenderMap, BaseFragment, createRenderMap, mergeRenderMaps, RenderMap } from '@codama/renderers-core';\nimport {\n extendVisitor,\n getByteSizeVisitor,\n getResolvedInstructionInputsVisitor,\n LinkableDictionary,\n NodeStack,\n pipe,\n recordLinkablesOnFirstVisitVisitor,\n recordNodeStackVisitor,\n ResolvedInstructionAccount,\n ResolvedInstructionInput,\n staticVisitor,\n visit,\n Visitor,\n} from '@codama/visitors-core';\n\nimport { ContextMap } from './ContextMap';\nimport { getTypeManifestVisitor } from './getTypeManifestVisitor';\nimport { ImportMap } from './ImportMap';\nimport { renderInstructionDefaults } from './renderInstructionDefaults';\nimport {\n CustomDataOptions,\n getDefinedTypeNodesToExtract,\n getGpaFieldsFromAccount,\n getImportFromFactory,\n LinkOverrides,\n parseCustomDataOptions,\n render,\n} from './utils';\n\nexport type GetRenderMapOptions = {\n customAccountData?: CustomDataOptions[];\n customInstructionData?: CustomDataOptions[];\n dependencyMap?: Record<string, string>;\n internalNodes?: string[];\n linkOverrides?: LinkOverrides;\n nonScalarEnums?: string[];\n renderParentInstructions?: boolean;\n};\n\nexport function getRenderMapVisitor(options: GetRenderMapOptions = {}): Visitor<RenderMap<BaseFragment>> {\n const linkables = new LinkableDictionary();\n const stack = new NodeStack();\n let program: ProgramNode | null = null;\n\n const renderParentInstructions = options.renderParentInstructions ?? false;\n const dependencyMap = {\n generated: '..',\n hooked: '../../hooked',\n mplEssentials: '@metaplex-foundation/mpl-toolbox',\n mplToolbox: '@metaplex-foundation/mpl-toolbox',\n umi: '@metaplex-foundation/umi',\n umiSerializers: '@metaplex-foundation/umi/serializers',\n ...options.dependencyMap,\n\n // Custom relative dependencies to link generated files together.\n generatedAccounts: '../accounts',\n generatedErrors: '../errors',\n generatedInstructions: '../instructions',\n generatedPrograms: '../programs',\n generatedTypes: '../types',\n };\n const nonScalarEnums = (options.nonScalarEnums ?? []).map(camelCase);\n const internalNodes = (options.internalNodes ?? []).map(camelCase);\n const customAccountData = parseCustomDataOptions(options.customAccountData ?? [], 'AccountData');\n const customInstructionData = parseCustomDataOptions(options.customInstructionData ?? [], 'InstructionData');\n const getImportFrom = getImportFromFactory(options.linkOverrides ?? {}, customAccountData, customInstructionData);\n\n const typeManifestVisitor = getTypeManifestVisitor({\n customAccountData,\n customInstructionData,\n getImportFrom,\n linkables,\n nonScalarEnums,\n stack,\n });\n const resolvedInstructionInputVisitor = getResolvedInstructionInputsVisitor();\n const byteSizeVisitor = getByteSizeVisitor(linkables, { stack });\n\n function getInstructionAccountType(account: ResolvedInstructionAccount): string {\n if (account.isPda && account.isSigner === false) return 'Pda';\n if (account.isSigner === 'either') return 'PublicKey | Pda | Signer';\n return account.isSigner ? 'Signer' : 'PublicKey | Pda';\n }\n\n function getInstructionAccountImports(accounts: ResolvedInstructionAccount[]): ImportMap {\n const imports = new ImportMap();\n accounts.forEach(account => {\n if (account.isSigner !== true && !account.isPda) imports.add('umi', 'PublicKey');\n if (account.isSigner !== true) imports.add('umi', 'Pda');\n if (account.isSigner !== false) imports.add('umi', 'Signer');\n });\n return imports;\n }\n\n function getMergeConflictsForInstructionAccountsAndArgs(instruction: InstructionNode): string[] {\n const allNames = [\n ...instruction.accounts.map(account => account.name),\n ...instruction.arguments.map(field => field.name),\n ...(instruction.extraArguments ?? []).map(field => field.name),\n ];\n const duplicates = allNames.filter((e, i, a) => a.indexOf(e) !== i);\n return [...new Set(duplicates)];\n }\n\n return pipe(\n staticVisitor(() => createRenderMap()),\n v =>\n extendVisitor(v, {\n visitAccount(node) {\n const customData = customAccountData.get(node.name);\n const isLinked = !!customData;\n const typeManifest = visit(node, typeManifestVisitor);\n const imports = new ImportMap().mergeWith(\n typeManifest.strictImports,\n typeManifest.serializerImports,\n );\n if (!isLinked) {\n imports.mergeWith(typeManifest.looseImports);\n }\n imports\n .add('umi', [\n 'Account',\n 'assertAccountExists',\n 'Context',\n 'deserializeAccount',\n 'Pda',\n 'PublicKey',\n 'publicKey',\n 'RpcAccount',\n 'RpcGetAccountOptions',\n 'RpcGetAccountsOptions',\n ])\n .add('umiSerializers', !isLinked ? ['Serializer'] : [])\n .addAlias('umi', 'publicKey', 'toPublicKey');\n\n // Discriminator.\n const discriminator =\n (node.discriminators ?? []).find(d => !isNode(d, 'constantDiscriminatorNode')) ?? null;\n let resolvedDiscriminator:\n | SizeDiscriminatorNode\n | (FieldDiscriminatorNode & { value: string })\n | null = null;\n if (isNode(discriminator, 'fieldDiscriminatorNode')) {\n const discriminatorField = resolveNestedTypeNode(node.data).fields.find(\n f => f.name === discriminator.name,\n );\n const discriminatorValue = discriminatorField?.defaultValue\n ? visit(discriminatorField.defaultValue, typeManifestVisitor)\n : undefined;\n if (discriminatorValue) {\n imports.mergeWith(discriminatorValue.valueImports);\n resolvedDiscriminator = {\n ...discriminator,\n value: discriminatorValue.value,\n };\n }\n } else if (isNode(discriminator, 'sizeDiscriminatorNode')) {\n resolvedDiscriminator = discriminator;\n }\n\n // GPA Fields.\n const gpaFields = getGpaFieldsFromAccount(node, byteSizeVisitor).map(gpaField => {\n const gpaFieldManifest = visit(gpaField.type, typeManifestVisitor);\n imports.mergeWith(gpaFieldManifest.looseImports, gpaFieldManifest.serializerImports);\n return { ...gpaField, manifest: gpaFieldManifest };\n });\n let resolvedGpaFields: { argument: string; type: string } | null = null;\n if (gpaFields.length > 0) {\n imports.add('umi', ['gpaBuilder']);\n resolvedGpaFields = {\n argument: `{ ${gpaFields\n .map(f => {\n const offset = f.offset === null ? 'null' : `${f.offset}`;\n return `'${f.name}': [${offset}, ${f.manifest.serializer}]`;\n })\n .join(', ')} }`,\n type: `{ ${gpaFields.map(f => `'${f.name}': ${f.manifest.looseType}`).join(', ')} }`,\n };\n }\n\n // Seeds.\n const pda = node.pda ? linkables.get([...stack.getPath(), node.pda]) : undefined;\n const pdaSeeds = pda?.seeds ?? [];\n const seeds = pdaSeeds.map(seed => {\n if (isNode(seed, 'variablePdaSeedNode')) {\n const seedManifest = visit(seed.type, typeManifestVisitor);\n imports.mergeWith(seedManifest.looseImports, seedManifest.serializerImports);\n return { ...seed, typeManifest: seedManifest };\n }\n if (isNode(seed.value, 'programIdValueNode')) {\n imports\n .add('umiSerializers', 'publicKey')\n .addAlias('umiSerializers', 'publicKey', 'publicKeySerializer');\n return seed;\n }\n const seedManifest = visit(seed.type, typeManifestVisitor);\n imports.mergeWith(seedManifest.serializerImports);\n const valueManifest = visit(seed.value, typeManifestVisitor);\n imports.mergeWith(valueManifest.valueImports);\n return { ...seed, typeManifest: seedManifest, valueManifest };\n });\n if (seeds.length > 0) {\n imports.add('umi', ['Pda']);\n }\n const hasVariableSeeds = pdaSeeds.filter(isNodeFilter('variablePdaSeedNode')).length > 0;\n\n return createRenderMap(`accounts/${camelCase(node.name)}.ts`, {\n content: render('accountsPage.njk', {\n account: node,\n customData,\n discriminator: resolvedDiscriminator,\n gpaFields: resolvedGpaFields,\n hasVariableSeeds,\n imports: imports.toString(dependencyMap),\n program,\n seeds,\n typeManifest,\n }),\n });\n },\n\n visitDefinedType(node) {\n const pascalCaseName = pascalCase(node.name);\n const typeManifest = visit(node, typeManifestVisitor);\n const imports = new ImportMap()\n .mergeWithManifest(typeManifest)\n .add('umiSerializers', ['Serializer'])\n .remove('generatedTypes', [\n pascalCaseName,\n `${pascalCaseName}Args`,\n `get${pascalCaseName}Serializer`,\n ]);\n\n return createRenderMap(`types/${camelCase(node.name)}.ts`, {\n content: render('definedTypesPage.njk', {\n definedType: node,\n imports: imports.toString({\n ...dependencyMap,\n generatedTypes: '.',\n }),\n isDataEnum: isNode(node.type, 'enumTypeNode') && isDataEnum(node.type),\n typeManifest,\n }),\n });\n },\n\n visitInstruction(node) {\n // Imports and interfaces.\n const interfaces = new ContextMap().add('programs');\n const imports = new ImportMap()\n .add('umi', ['Context', 'TransactionBuilder', 'transactionBuilder'])\n .add('shared', ['ResolvedAccount', 'ResolvedAccountsWithIndices', 'getAccountMetasAndSigners']);\n\n // Instruction helpers.\n const customData = customInstructionData.get(node.name);\n const linkedDataArgs = !!customData;\n const hasAccounts = node.accounts.length > 0;\n const hasData = linkedDataArgs || node.arguments.length > 0;\n const hasDataArgs =\n linkedDataArgs ||\n node.arguments.filter(field => field.defaultValueStrategy !== 'omitted').length > 0;\n const hasExtraArgs =\n (node.extraArguments ?? []).filter(field => field.defaultValueStrategy !== 'omitted').length >\n 0;\n const hasAnyArgs = hasDataArgs || hasExtraArgs;\n const allArgumentsWithDefaultValue = [\n ...node.arguments.filter(a => a.defaultValue && !isNode(a.defaultValue, VALUE_NODES)),\n ...(node.extraArguments ?? []).filter(a => a.defaultValue),\n ];\n const hasArgDefaults = allArgumentsWithDefaultValue.length > 0;\n const hasArgResolvers = allArgumentsWithDefaultValue.some(a =>\n isNode(a.defaultValue, 'resolverValueNode'),\n );\n const hasAccountResolvers = node.accounts.some(a => isNode(a.defaultValue, 'resolverValueNode'));\n const byteDelta = node.byteDeltas?.[0] ?? undefined;\n const hasByteResolver = byteDelta && isNode(byteDelta.value, 'resolverValueNode');\n let remainingAccounts = node.remainingAccounts?.[0] ?? undefined;\n if (\n remainingAccounts &&\n isNode(remainingAccounts.value, 'argumentValueNode') &&\n getAllInstructionArguments(node).every(arg => arg.name !== remainingAccounts?.value.name)\n ) {\n remainingAccounts = undefined;\n }\n const hasRemainingAccountsResolver =\n remainingAccounts && isNode(remainingAccounts.value, 'resolverValueNode');\n const hasResolvers =\n hasArgResolvers || hasAccountResolvers || hasByteResolver || hasRemainingAccountsResolver;\n const hasResolvedArgs = hasDataArgs || hasArgDefaults || hasResolvers;\n if (hasResolvers) {\n interfaces.add(['eddsa', 'identity', 'payer']);\n }\n\n // canMergeAccountsAndArgs\n let canMergeAccountsAndArgs = false;\n if (!linkedDataArgs) {\n const accountsAndArgsConflicts = getMergeConflictsForInstructionAccountsAndArgs(node);\n if (accountsAndArgsConflicts.length > 0) {\n logWarn(\n `[JavaScript Umi] Accounts and args of instruction [${node.name}] have the following ` +\n `conflicting attributes [${accountsAndArgsConflicts.join(', ')}]. ` +\n `Thus, they could not be merged into a single input object. ` +\n 'You may want to rename the conflicting attributes.',\n );\n }\n canMergeAccountsAndArgs = accountsAndArgsConflicts.length === 0;\n }\n\n // Resolved inputs.\n let argObject = canMergeAccountsAndArgs ? 'input' : 'args';\n argObject = hasResolvedArgs ? 'resolvedArgs' : argObject;\n const resolvedInputs = visit(node, resolvedInstructionInputVisitor).map(\n (input: ResolvedInstructionInput) => {\n const renderedInput = renderInstructionDefaults(\n input,\n typeManifestVisitor,\n parseOptionalAccountStrategy(node.optionalAccountStrategy),\n argObject,\n getImportFrom,\n );\n imports.mergeWith(renderedInput.imports);\n interfaces.mergeWith(renderedInput.interfaces);\n return { ...input, render: renderedInput.render };\n },\n );\n const resolvedInputsWithDefaults = resolvedInputs.filter(\n input => input.defaultValue !== undefined && input.render !== '',\n );\n const argsWithDefaults = resolvedInputsWithDefaults\n .filter(isNodeFilter('instructionArgumentNode'))\n .map(input => input.name);\n\n // Accounts.\n const accounts = node.accounts.map(account => {\n const hasDefaultValue = !!account.defaultValue;\n const resolvedAccount = resolvedInputs.find(\n input => input.kind === 'instructionAccountNode' && input.name === account.name,\n ) as ResolvedInstructionAccount;\n return {\n ...resolvedAccount,\n hasDefaultValue,\n optionalSign: hasDefaultValue || account.isOptional ? '?' : '',\n type: getInstructionAccountType(resolvedAccount),\n };\n });\n imports.mergeWith(getInstructionAccountImports(accounts));\n\n // Data Args.\n const dataArgManifest = visit(node, typeManifestVisitor);\n if (linkedDataArgs || hasData) {\n imports.mergeWith(dataArgManifest.looseImports, dataArgManifest.serializerImports);\n }\n if (!linkedDataArgs) {\n imports.mergeWith(dataArgManifest.strictImports);\n }\n if (!linkedDataArgs && hasData) {\n imports.add('umiSerializers', ['Serializer']);\n }\n\n // Extra args.\n const extraArgStruct = definedTypeNode({\n name: `${node.name}InstructionExtra`,\n type: structTypeNodeFromInstructionArgumentNodes(node.extraArguments ?? []),\n });\n const extraArgManifest = visit(extraArgStruct, typeManifestVisitor);\n imports.mergeWith(extraArgManifest.looseImports);\n\n // Arg defaults.\n allArgumentsWithDefaultValue.forEach(argument => {\n if (isNode(argument.defaultValue, 'resolverValueNode')) {\n imports.add(getImportFrom(argument.defaultValue), camelCase(argument.defaultValue.name));\n }\n });\n if (argsWithDefaults.length > 0) {\n imports.add('shared', ['PickPartial']);\n }\n\n // Bytes created on chain.\n if (byteDelta && byteDelta.withHeader) {\n imports.add('umi', 'ACCOUNT_HEADER_SIZE');\n }\n if (byteDelta && isNode(byteDelta.value, 'accountLinkNode')) {\n const accountName = pascalCase(byteDelta.value.name);\n imports.add(getImportFrom(byteDelta.value), `get${accountName}Size`);\n } else if (byteDelta && isNode(byteDelta.value, 'resolverValueNode')) {\n imports.add(getImportFrom(byteDelta.value), camelCase(byteDelta.value.name));\n }\n\n // Remaining accounts.\n if (remainingAccounts && isNode(remainingAccounts.value, 'resolverValueNode')) {\n imports.add(getImportFrom(remainingAccounts.value), camelCase(remainingAccounts.value.name));\n }\n\n return createRenderMap(`instructions/${camelCase(node.name)}.ts`, {\n content: render('instructionsPage.njk', {\n accounts,\n argsWithDefaults,\n byteDelta,\n canMergeAccountsAndArgs,\n customData,\n dataArgManifest,\n extraArgManifest,\n hasAccountResolvers,\n hasAccounts,\n hasAnyArgs,\n hasArgDefaults,\n hasArgResolvers,\n hasByteResolver,\n hasData,\n hasDataArgs,\n hasExtraArgs,\n hasRemainingAccountsResolver,\n hasResolvedArgs,\n hasResolvers,\n imports: imports.toString(dependencyMap),\n instruction: node,\n interfaces: interfaces.toString(),\n program,\n remainingAccounts,\n resolvedInputs,\n resolvedInputsWithDefaults,\n }),\n });\n },\n\n visitProgram(node, { self }) {\n program = node;\n const pascalCaseName = pascalCase(node.name);\n const customDataDefinedType = [\n ...getDefinedTypeNodesToExtract(node.accounts, customAccountData),\n ...getDefinedTypeNodesToExtract(node.instructions, customInstructionData),\n ];\n const renders = pipe(\n mergeRenderMaps([\n ...node.accounts.map(a => visit(a, self)),\n ...node.definedTypes.map(t => visit(t, self)),\n ...customDataDefinedType.map(t => visit(t, self)),\n ...getAllInstructionsWithSubs(node, {\n leavesOnly: !renderParentInstructions,\n }).map(ix => visit(ix, self)),\n ]),\n r =>\n addToRenderMap(r, `errors/${camelCase(node.name)}.ts`, {\n content: render('errorsPage.njk', {\n errors: node.errors,\n imports: new ImportMap()\n .add('umi', ['ProgramError', 'Program'])\n .toString(dependencyMap),\n program: node,\n }),\n }),\n r =>\n addToRenderMap(r, `programs/${camelCase(node.name)}.ts`, {\n content: render('programsPage.njk', {\n imports: new ImportMap()\n .add('umi', ['ClusterFilter', 'Context', 'Program', 'PublicKey'])\n .add('errors', [\n `get${pascalCaseName}ErrorFromCode`,\n `get${pascalCaseName}ErrorFromName`,\n ])\n .toString(dependencyMap),\n program: node,\n }),\n }),\n );\n program = null;\n return renders;\n },\n\n visitRoot(node, { self }) {\n const isNotInternal = (n: { name: CamelCaseString }) => !internalNodes.includes(n.name);\n const programsToExport = getAllPrograms(node).filter(isNotInternal);\n const accountsToExport = getAllAccounts(node).filter(isNotInternal);\n const instructionsToExport = getAllInstructionsWithSubs(node, {\n leavesOnly: !renderParentInstructions,\n }).filter(isNotInternal);\n const definedTypesToExport = getAllDefinedTypes(node).filter(isNotInternal);\n const hasAnythingToExport =\n programsToExport.length > 0 ||\n accountsToExport.length > 0 ||\n instructionsToExport.length > 0 ||\n definedTypesToExport.length > 0;\n\n const ctx = {\n accountsToExport,\n definedTypesToExport,\n hasAnythingToExport,\n instructionsToExport,\n programsToExport,\n root: node,\n };\n\n return mergeRenderMaps([\n createRenderMap({\n ['accounts/index.ts']:\n accountsToExport.length > 0 ? { content: render('accountsIndex.njk', ctx) } : undefined,\n ['errors/index.ts']:\n programsToExport.length > 0 ? { content: render('errorsIndex.njk', ctx) } : undefined,\n ['index.ts']: { content: render('rootIndex.njk', ctx) },\n ['instructions/index.ts']:\n instructionsToExport.length > 0\n ? { content: render('instructionsIndex.njk', ctx) }\n : undefined,\n ['programs/index.ts']:\n programsToExport.length > 0 ? { content: render('programsIndex.njk', ctx) } : undefined,\n ['shared/index.ts']: hasAnythingToExport\n ? { content: render('sharedPage.njk', ctx) }\n : undefined,\n ['types/index.ts']:\n definedTypesToExport.length > 0\n ? { content: render('definedTypesIndex.njk', ctx) }\n : undefined,\n }),\n ...getAllPrograms(node).map(p => visit(p, self)),\n ]);\n },\n }),\n v => recordNodeStackVisitor(v, stack),\n v => recordLinkablesOnFirstVisitVisitor(v, linkables),\n );\n}\n","export type ContextInterface =\n | 'downloader'\n | 'eddsa'\n | 'http'\n | 'identity'\n | 'payer'\n | 'programs'\n | 'rpc'\n | 'transactions'\n | 'uploader';\n\nexport class ContextMap {\n protected readonly _interfaces: Set<ContextInterface> = new Set();\n\n add(contextInterface: ContextInterface | ContextInterface[]): ContextMap {\n if (Array.isArray(contextInterface)) {\n contextInterface.forEach(i => this._interfaces.add(i));\n } else {\n this._interfaces.add(contextInterface);\n }\n return this;\n }\n\n remove(contextInterface: ContextInterface | ContextInterface[]): ContextMap {\n if (Array.isArray(contextInterface)) {\n contextInterface.forEach(i => this._interfaces.delete(i));\n } else {\n this._interfaces.delete(contextInterface);\n }\n return this;\n }\n\n mergeWith(...others: ContextMap[]): ContextMap {\n others.forEach(other => this.add([...other._interfaces]));\n return this;\n }\n\n isEmpty(): boolean {\n return this._interfaces.size === 0;\n }\n\n toString(): string {\n const contextInterfaces = [...this._interfaces]\n .sort()\n .map(i => `\"${i}\"`)\n .join(' | ');\n return `Pick<Context, ${contextInterfaces}>`;\n }\n}\n","import { CODAMA_ERROR__RENDERERS__UNSUPPORTED_NODE, CodamaError } from '@codama/errors';\nimport {\n ArrayTypeNode,\n camelCase,\n CamelCaseString,\n isInteger,\n isNode,\n isScalarEnum,\n isUnsignedInteger,\n NumberTypeNode,\n parseDocs,\n pascalCase,\n REGISTERED_TYPE_NODE_KINDS,\n REGISTERED_VALUE_NODE_KINDS,\n resolveNestedTypeNode,\n structFieldTypeNode,\n structTypeNode,\n structTypeNodeFromInstructionArgumentNodes,\n TypeNode,\n} from '@codama/nodes';\nimport {\n extendVisitor,\n LinkableDictionary,\n NodeStack,\n pipe,\n recordNodeStackVisitor,\n staticVisitor,\n visit,\n Visitor,\n} from '@codama/visitors-core';\n\nimport { ImportMap } from './ImportMap';\nimport { getBytesFromBytesValueNode, GetImportFromFunction, jsDocblock, ParsedCustomDataOptions } from './utils';\n\nexport type TypeManifest = {\n isEnum: boolean;\n looseImports: ImportMap;\n looseType: string;\n serializer: string;\n serializerImports: ImportMap;\n strictImports: ImportMap;\n strictType: string;\n value: string;\n valueImports: ImportMap;\n};\n\nfunction typeManifest(): TypeManifest {\n return {\n isEnum: false,\n looseImports: new ImportMap(),\n looseType: '',\n serializer: '',\n serializerImports: new ImportMap(),\n strictImports: new ImportMap(),\n strictType: '',\n value: '',\n valueImports: new ImportMap(),\n };\n}\n\nexport function getTypeManifestVisitor(input: {\n customAccountData: ParsedCustomDataOptions;\n customInstructionData: ParsedCustomDataOptions;\n getImportFrom: GetImportFromFunction;\n linkables: LinkableDictionary;\n nonScalarEnums: CamelCaseString[];\n stack?: NodeStack;\n}) {\n const { linkables, nonScalarEnums, customAccountData, customInstructionData, getImportFrom } = input;\n const stack = input.stack ?? new NodeStack();\n let parentName: { loose: string; strict: string } | null = null;\n let parentSize: NumberTypeNode | number | null = null;\n\n return pipe(\n staticVisitor(\n () =>\n ({\n isEnum: false,\n looseImports: new ImportMap(),\n looseType: '',\n serializer: '',\n serializerImports: new ImportMap(),\n strictImports: new ImportMap(),\n strictType: '',\n value: '',\n valueImports: new ImportMap(),\n }) as TypeManifest,\n {\n keys: [\n ...REGISTERED_TYPE_NODE_KINDS,\n ...REGISTERED_VALUE_NODE_KINDS,\n 'definedTypeLinkNode',\n 'definedTypeNode',\n 'accountNode',\n 'instructionNode',\n ],\n },\n ),\n v =>\n extendVisitor(v, {\n visitAccount(account, { self }) {\n parentName = {\n loose: `${pascalCase(account.name)}AccountDataArgs`,\n strict: `${pascalCase(account.name)}AccountData`,\n };\n const link = customAccountData.get(account.name)?.linkNode;\n const manifest = link ? visit(link, self) : visit(account.data, self);\n parentName = null;\n return manifest;\n },\n\n visitAmountType(amountType, { self }) {\n const numberManifest = visit(amountType.number, self);\n const resolvedNode = resolveNestedTypeNode(amountType.number);\n if (!isUnsignedInteger(resolvedNode)) {\n throw new Error(\n `Amount wrappers can only be applied to unsigned ` +\n `integer types. Got format [${resolvedNode.format}].`,\n );\n }\n const { unit, decimals } = amountType;\n const idAndDecimals = `'${unit ?? 'Unknown'}', ${decimals}`;\n const isSolAmount = unit === 'SOL' && decimals === 9;\n const amountTypeString = isSolAmount ? 'SolAmount' : `Amount<${idAndDecimals}>`;\n const amountImport = isSolAmount ? 'SolAmount' : 'Amount';\n numberManifest.strictImports.add('umi', amountImport);\n numberManifest.looseImports.add('umi', amountImport);\n numberManifest.serializerImports.add('umi', 'mapAmountSerializer');\n return {\n ...numberManifest,\n looseType: amountTypeString,\n serializer: `mapAmountSerializer(${numberManifest.serializer}, ${idAndDecimals})`,\n strictType: amountTypeString,\n };\n },\n\n visitArrayType(arrayType, { self }) {\n const childManifest = visit(arrayType.item, self);\n childManifest.serializerImports.add('umiSerializers', 'array');\n const sizeOption = getArrayLikeSizeOption(arrayType.count, childManifest, self);\n const options = sizeOption ? `, { ${sizeOption} }` : '';\n return {\n ...childManifest,\n looseType: `Array<${childManifest.looseType}>`,\n serializer: `array(${childManifest.serializer + options})`,\n strictType: `Array<${childManifest.strictType}>`,\n };\n },\n\n visitArrayValue(node, { self }) {\n const list = node.items.map(value => visit(value, self));\n return {\n ...typeManifest(),\n value: `[${list.map(c => c.value).join(', ')}]`,\n valueImports: new ImportMap().mergeWith(...list.map(c => c.valueImports)),\n };\n },\n\n visitBooleanType(booleanType, { self }) {\n const looseImports = new ImportMap();\n const strictImports = new ImportMap();\n const serializerImports = new ImportMap().add('umiSerializers', 'bool');\n let sizeSerializer = '';\n const resolvedSize = resolveNestedTypeNode(booleanType.size);\n if (resolvedSize.format !== 'u8' || resolvedSize.endian !== 'le') {\n const size = visit(booleanType.size, self);\n looseImports.mergeWith(size.looseImports);\n strictImports.mergeWith(size.strictImports);\n serializerImports.mergeWith(size.serializerImports);\n sizeSerializer = `{ size: ${size.serializer} }`;\n }\n\n return {\n isEnum: false,\n looseImports,\n looseType: 'boolean',\n serializer: `bool(${sizeSerializer})`,\n serializerImports,\n strictImports,\n strictType: 'boolean',\n value: '',\n valueImports: new ImportMap(),\n };\n },\n\n visitBooleanValue(node) {\n return {\n ...typeManifest(),\n value: JSON.stringify(node.boolean),\n };\n },\n\n visitBytesType(_bytesType, { self }) {\n const strictImports = new ImportMap();\n const looseImports = new ImportMap();\n const serializerImports = new ImportMap().add('umiSerializers', 'bytes');\n const options: string[] = [];\n\n // Size option.\n if (typeof parentSize === 'number') {\n options.push(`size: ${parentSize}`);\n } else if (parentSize) {\n const prefix = visit(parentSize, self);\n strictImports.mergeWith(prefix.strictImports);\n looseImports.mergeWith(prefix.looseImports);\n serializerImports.mergeWith(prefix.serializerImports);\n options.push(`size: ${prefix.serializer}`);\n }\n\n const optionsAsString = options.length > 0 ? `{ ${options.join(', ')} }` : '';\n\n return {\n isEnum: false,\n looseImports,\n looseType: 'Uint8Array',\n serializer: `bytes(${optionsAsString})`,\n serializerImports,\n strictImports,\n strictType: 'Uint8Array',\n value: '',\n valueImports: new ImportMap(),\n };\n },\n\n visitBytesValue(node) {\n const bytes = getBytesFromBytesValueNode(node);\n return {\n ...typeManifest(),\n value: `new Uint8Array([${Array.from(bytes).join(', ')}])`,\n };\n },\n\n visitConstantValue(node, { self }) {\n if (isNode(node.type, 'bytesTypeNode') && isNode(node.value, 'bytesValueNode')) {\n return visit(node.value, self);\n }\n const imports = new ImportMap();\n const value = visit(node.value, self);\n imports.mergeWith(value.valueImports);\n const type = visit(node.type, self);\n imports.mergeWith(type.serializerImports);\n return {\n ...typeManifest(),\n value: `${type.serializer}.serialize(${value.value})`,\n valueImports: imports,\n };\n },\n\n visitDateTimeType(dateTimeType, { self }) {\n const numberManifest = visit(dateTimeType.number, self);\n const dateTimeNumber = resolveNestedTypeNode(dateTimeType.number);\n if (!isInteger(dateTimeNumber)) {\n throw new Error(\n `DateTime wrappers can only be applied to integer ` +\n `types. Got format [${dateTimeNumber.format}].`,\n );\n }\n numberManifest.strictImports.add('umi', 'DateTime');\n numberManifest.looseImports.add('umi', 'DateTimeInput');\n numberManifest.serializerImports.add('umi', 'mapDateTimeSerializer');\n return {\n ...numberManifest,\n looseType: `DateTimeInput`,\n serializer: `mapDateTimeSerializer(${numberManifest.serializer})`,\n strictType: `DateTime`,\n };\n },\n\n visitDefinedType(definedType, { self }) {\n parentName = {\n loose: `${pascalCase(definedType.name)}Args`,\n strict: pascalCase(definedType.name),\n };\n const manifest = visit(definedType.type, self);\n parentName = null;\n return manifest;\n },\n\n visitDefinedTypeLink(node) {\n const pascalCaseDefinedType = pascalCase(node.name);\n const serializerName = `get${pascalCaseDefinedType}Serializer`;\n const importFrom = getImportFrom(node);\n\n return {\n isEnum: false,\n looseImports: new ImportMap().add(importFrom, `${pascalCaseDefinedType}Args`),\n looseType: `${pascalCaseDefinedType}Args`,\n serializer: `${serializerName}()`,\n serializerImports: new ImportMap().add(importFrom, serializerName),\n strictImports: new ImportMap().add(importFrom, pascalCaseDefinedType),\n strictType: pascalCaseDefinedType,\n value: '',\n valueImports: new ImportMap(),\n };\n },\n\n visitEnumEmptyVariantType(enumEmptyVariantType) {\n const name = pascalCase(enumEmptyVariantType.name);\n const kindAttribute = `__kind: \"${name}\"`;\n return {\n isEnum: false,\n looseImports: new ImportMap(),\n looseType: `{ ${kindAttribute} }`,\n serializer: `['${name}', unit()]`,\n serializerImports: new ImportMap().add('umiSerializers', 'unit'),\n strictImports: new ImportMap(),\n strictType: `{ ${kindAttribute} }`,\n value: '',\n valueImports: new ImportMap(),\n };\n },\n\n visitEnumStructVariantType(enumStructVariantType, { self }) {\n const name = pascalCase(enumStructVariantType.name);\n const kindAttribute = `__kind: \"${name}\"`;\n const type = visit(enumStructVariantType.struct, self);\n return {\n ...type,\n looseType: `{ ${kindAttribute},${type.looseType.slice(1, -1)}}`,\n serializer: `['${name}', ${type.serializer}]`,\n strictType: `{ ${kindAttribute},${type.strictType.slice(1, -1)}}`,\n };\n },\n\n visitEnumTupleVariantType(enumTupleVariantType, { self }) {\n const name = pascalCase(enumTupleVariantType.name);\n const kindAttribute = `__kind: \"${name}\"`;\n const struct = structTypeNode([\n structFieldTypeNode({\n name: 'fields',\n type: enumTupleVariantType.tuple,\n }),\n ]);\n const type = visit(struct, self);\n return {\n ...type,\n looseType: `{ ${kindAttribute},${type.looseType.slice(1, -1)}}`,\n serializer: `['${name}', ${type.serializer}]`,\n strictType: `{ ${kindAttribute},${type.strictType.slice(1, -1)}}`,\n value: '',\n valueImports: new ImportMap(),\n };\n },\n\n visitEnumType(enumType, { self }) {\n const strictImports = new ImportMap();\n const looseImports = new ImportMap();\n const serializerImports = new ImportMap();\n\n const variantNames = enumType.variants.map(variant => pascalCase(variant.name));\n const currentParentName = { ...parentName };\n parentName = null;\n const options: string[] = [];\n\n const enumSize = resolveNestedTypeNode(enumType.size);\n if (enumSize.format !== 'u8' || enumSize.endian !== 'le') {\n const sizeManifest = visit(enumType.size, self);\n strictImports.mergeWith(sizeManifest.strictImports);\n looseImports.mergeWith(sizeManifest.looseImports);\n serializerImports.mergeWith(sizeManifest.serializerImports);\n options.push(`size: ${sizeManifest.serializer}`);\n }\n\n if (isScalarEnum(enumType)) {\n if (currentParentName === null) {\n