UNPKG

@codama/renderers-js

Version:

JavaScript renderer compatible with the Solana Kit library

1,180 lines (1,150 loc) 137 kB
// src/ImportMap.ts var DEFAULT_EXTERNAL_MODULE_MAP = { solanaAccounts: "@solana/kit", solanaAddresses: "@solana/kit", solanaCodecsCore: "@solana/kit", solanaCodecsDataStructures: "@solana/kit", solanaCodecsNumbers: "@solana/kit", solanaCodecsStrings: "@solana/kit", solanaErrors: "@solana/kit", solanaInstructions: "@solana/kit", solanaOptions: "@solana/kit", solanaPrograms: "@solana/kit", solanaRpcTypes: "@solana/kit", solanaSigners: "@solana/kit" }; var DEFAULT_GRANULAR_EXTERNAL_MODULE_MAP = { solanaAccounts: "@solana/accounts", solanaAddresses: "@solana/addresses", solanaCodecsCore: "@solana/codecs", solanaCodecsDataStructures: "@solana/codecs", solanaCodecsNumbers: "@solana/codecs", solanaCodecsStrings: "@solana/codecs", solanaErrors: "@solana/errors", solanaInstructions: "@solana/instructions", solanaOptions: "@solana/codecs", solanaPrograms: "@solana/programs", solanaRpcTypes: "@solana/rpc-types", solanaSigners: "@solana/signers" }; var DEFAULT_INTERNAL_MODULE_MAP = { errors: "../errors", generated: "..", generatedAccounts: "../accounts", generatedErrors: "../errors", generatedInstructions: "../instructions", generatedPdas: "../pdas", generatedPrograms: "../programs", generatedTypes: "../types", hooked: "../../hooked", shared: "../shared", types: "../types" }; var ImportMap = class { _imports = /* @__PURE__ */ new Map(); _aliases = /* @__PURE__ */ new Map(); add(module, imports) { const newImports = new Set(typeof imports === "string" ? [imports] : imports); if (newImports.size === 0) return this; const currentImports = this._imports.get(module) ?? /* @__PURE__ */ new Set(); newImports.forEach((i) => currentImports.add(i)); this._imports.set(module, currentImports); return this; } remove(module, imports) { const importsToRemove = new Set(typeof imports === "string" ? [imports] : imports); if (importsToRemove.size === 0) return this; const currentImports = this._imports.get(module) ?? /* @__PURE__ */ new Set(); importsToRemove.forEach((i) => currentImports.delete(i)); if (currentImports.size === 0) { this._imports.delete(module); } else { this._imports.set(module, currentImports); } return this; } mergeWith(...others) { others.forEach((rawOther) => { const other = "imports" in rawOther ? rawOther.imports : rawOther; other._imports.forEach((imports, module) => { this.add(module, imports); }); other._aliases.forEach((aliases, module) => { Object.entries(aliases).forEach(([name, alias]) => { this.addAlias(module, name, alias); }); }); }); return this; } mergeWithManifest(manifest) { return this.mergeWith(manifest.strictType, manifest.looseType, manifest.encoder, manifest.decoder); } addAlias(module, name, alias) { const currentAliases = this._aliases.get(module) ?? {}; currentAliases[name] = alias; this._aliases.set(module, currentAliases); return this; } isEmpty() { return this._imports.size === 0; } resolve(dependencies = {}, useGranularImports = false) { const aliasedMap = new Map( [...this._imports.entries()].map(([module, imports]) => { const aliasMap = this._aliases.get(module) ?? {}; const joinedImports = [...imports].map((i) => aliasMap[i] ? `${i} as ${aliasMap[i]}` : i); return [module, new Set(joinedImports)]; }) ); const dependencyMap = { ...useGranularImports ? DEFAULT_GRANULAR_EXTERNAL_MODULE_MAP : DEFAULT_EXTERNAL_MODULE_MAP, ...DEFAULT_INTERNAL_MODULE_MAP, ...dependencies }; const resolvedMap = /* @__PURE__ */ new Map(); aliasedMap.forEach((imports, module) => { const resolvedModule = dependencyMap[module] ?? module; const currentImports = resolvedMap.get(resolvedModule) ?? /* @__PURE__ */ new Set(); imports.forEach((i) => currentImports.add(i)); resolvedMap.set(resolvedModule, currentImports); }); return resolvedMap; } toString(dependencies = {}, useGranularImports = false) { return [...this.resolve(dependencies, useGranularImports).entries()].sort(([a], [b]) => { const aIsRelative = a.startsWith("."); const bIsRelative = b.startsWith("."); if (aIsRelative && !bIsRelative) return 1; if (!aIsRelative && bIsRelative) return -1; return a.localeCompare(b); }).map(([module, imports]) => { const joinedImports = [...imports].sort().filter((i) => { const name = i.split(" "); if (name.length > 1) { return !imports.has(name[1]); } return true; }).join(", "); return `import { ${joinedImports} } from '${module}';`; }).join("\n"); } }; // src/fragments/accountFetchHelpers.ts import { getLastNodeFromPath } from "@codama/visitors-core"; // src/fragments/common.ts import { join as join2 } from "path"; // src/utils/async.ts import { accountValueNode, argumentValueNode, isNode } from "@codama/nodes"; import { deduplicateInstructionDependencies } from "@codama/visitors-core"; function hasAsyncFunction(instructionNode, resolvedInputs, asyncResolvers) { const hasByteDeltasAsync = (instructionNode.byteDeltas ?? []).some( ({ value }) => isNode(value, "resolverValueNode") && asyncResolvers.includes(value.name) ); const hasRemainingAccountsAsync = (instructionNode.remainingAccounts ?? []).some( ({ value }) => isNode(value, "resolverValueNode") && asyncResolvers.includes(value.name) ); return hasAsyncDefaultValues(resolvedInputs, asyncResolvers) || hasByteDeltasAsync || hasRemainingAccountsAsync; } function hasAsyncDefaultValues(resolvedInputs, asyncResolvers) { return resolvedInputs.some( (input) => !!input.defaultValue && isAsyncDefaultValue(input.defaultValue, asyncResolvers) ); } function isAsyncDefaultValue(defaultValue, asyncResolvers) { switch (defaultValue.kind) { case "pdaValueNode": return true; case "resolverValueNode": return asyncResolvers.includes(defaultValue.name); case "conditionalValueNode": return isAsyncDefaultValue(defaultValue.condition, asyncResolvers) || (defaultValue.ifFalse == null ? false : isAsyncDefaultValue(defaultValue.ifFalse, asyncResolvers)) || (defaultValue.ifTrue == null ? false : isAsyncDefaultValue(defaultValue.ifTrue, asyncResolvers)); default: return false; } } function getInstructionDependencies(input, asyncResolvers, useAsync) { if (isNode(input, "instructionNode")) { return deduplicateInstructionDependencies([ ...input.accounts.flatMap((x) => getInstructionDependencies(x, asyncResolvers, useAsync)), ...input.arguments.flatMap((x) => getInstructionDependencies(x, asyncResolvers, useAsync)), ...(input.extraArguments ?? []).flatMap((x) => getInstructionDependencies(x, asyncResolvers, useAsync)) ]); } if (!input.defaultValue) return []; const getNestedDependencies = (defaultValue) => { if (!defaultValue) return []; return getInstructionDependencies({ ...input, defaultValue }, asyncResolvers, useAsync); }; if (isNode(input.defaultValue, ["accountValueNode", "accountBumpValueNode"])) { return [accountValueNode(input.defaultValue.name)]; } if (isNode(input.defaultValue, ["argumentValueNode"])) { return [argumentValueNode(input.defaultValue.name)]; } if (isNode(input.defaultValue, "pdaValueNode")) { const dependencies = /* @__PURE__ */ new Map(); input.defaultValue.seeds.forEach((seed) => { if (isNode(seed.value, ["accountValueNode", "argumentValueNode"])) { dependencies.set(seed.value.name, { ...seed.value }); } }); return [...dependencies.values()]; } if (isNode(input.defaultValue, "resolverValueNode")) { const isSynchronousResolver = !asyncResolvers.includes(input.defaultValue.name); if (useAsync || isSynchronousResolver) { return input.defaultValue.dependsOn ?? []; } } if (isNode(input.defaultValue, "conditionalValueNode")) { return deduplicateInstructionDependencies([ ...getNestedDependencies(input.defaultValue.condition), ...getNestedDependencies(input.defaultValue.ifTrue), ...getNestedDependencies(input.defaultValue.ifFalse) ]); } return []; } // src/utils/codecs.ts import { getBase16Encoder, getBase58Encoder, getBase64Encoder, getUtf8Encoder } from "@solana/codecs-strings"; function getBytesFromBytesValueNode(node) { switch (node.encoding) { case "utf8": return getUtf8Encoder().encode(node.data); case "base16": return getBase16Encoder().encode(node.data); case "base58": return getBase58Encoder().encode(node.data); case "base64": default: return getBase64Encoder().encode(node.data); } } // src/utils/customData.ts import { camelCase, definedTypeLinkNode, definedTypeNode, isNode as isNode2, structTypeNodeFromInstructionArgumentNodes } from "@codama/nodes"; var parseCustomDataOptions = (customDataOptions, defaultSuffix) => new Map( customDataOptions.map((o) => { const options = typeof o === "string" ? { name: o } : o; const importAs = camelCase(options.importAs ?? `${options.name}${defaultSuffix}`); const importFrom = options.importFrom ?? "hooked"; return [ camelCase(options.name), { extract: options.extract ?? false, extractAs: options.extractAs ? camelCase(options.extractAs) : importAs, importAs, importFrom, linkNode: definedTypeLinkNode(importAs) } ]; }) ); var getDefinedTypeNodesToExtract = (nodes, parsedCustomDataOptions) => nodes.flatMap((node) => { const options = parsedCustomDataOptions.get(node.name); if (!options || !options.extract) return []; if (isNode2(node, "accountNode")) { return [definedTypeNode({ name: options.extractAs, type: { ...node.data } })]; } return [ definedTypeNode({ name: options.extractAs, type: structTypeNodeFromInstructionArgumentNodes(node.arguments) }) ]; }); // src/utils/linkOverrides.ts import { CODAMA_ERROR__UNEXPECTED_NODE_KIND, CodamaError } from "@codama/errors"; function getImportFromFactory(overrides, customAccountData, customInstructionData) { const customDataOverrides = Object.fromEntries( [...customAccountData.values(), ...customInstructionData.values()].map(({ importFrom, importAs }) => [ importAs, importFrom ]) ); const linkOverrides = { accounts: overrides.accounts ?? {}, definedTypes: { ...customDataOverrides, ...overrides.definedTypes }, instructions: overrides.instructions ?? {}, pdas: overrides.pdas ?? {}, programs: overrides.programs ?? {}, resolvers: overrides.resolvers ?? {} }; return (node) => { const kind = node.kind; switch (kind) { case "accountLinkNode": return linkOverrides.accounts[node.name] ?? "generatedAccounts"; case "definedTypeLinkNode": return linkOverrides.definedTypes[node.name] ?? "generatedTypes"; case "instructionLinkNode": return linkOverrides.instructions[node.name] ?? "generatedInstructions"; case "pdaLinkNode": return linkOverrides.pdas[node.name] ?? "generatedPdas"; case "programLinkNode": return linkOverrides.programs[node.name] ?? "generatedPrograms"; case "resolverValueNode": return linkOverrides.resolvers[node.name] ?? "hooked"; default: throw new CodamaError(CODAMA_ERROR__UNEXPECTED_NODE_KIND, { expectedKinds: [ "AccountLinkNode", "DefinedTypeLinkNode", "InstructionLinkNode", "PdaLinkNode", "ProgramLinkNode", "resolverValueNode" ], kind, node }); } }; } // src/utils/render.ts import { dirname as pathDirname, join } from "path"; import { fileURLToPath } from "url"; import { camelCase as camelCase2, kebabCase, pascalCase, snakeCase, titleCase } from "@codama/nodes"; import nunjucks from "nunjucks"; function jsDocblock(docs) { if (docs.length <= 0) return ""; if (docs.length === 1) return `/** ${docs[0]} */ `; const lines = docs.map((doc) => ` * ${doc}`); return `/** ${lines.join("\n")} */ `; } var render = (template, context, options) => { const dirname = true ? pathDirname(fileURLToPath(import.meta.url)) : __dirname; const templates = false ? join(dirname, "..", "..", "public", "templates") : join(dirname, "templates"); const env = nunjucks.configure(templates, { autoescape: false, trimBlocks: true, ...options }); env.addFilter("pascalCase", pascalCase); env.addFilter("camelCase", camelCase2); env.addFilter("snakeCase", snakeCase); env.addFilter("kebabCase", kebabCase); env.addFilter("titleCase", titleCase); env.addFilter("jsDocblock", jsDocblock); return env.render(template, context); }; // src/fragments/common.ts function fragment(render2, imports) { return new Fragment(render2, imports); } function fragmentFromTemplate(fragmentFile, context, options) { return fragment(render(join2("fragments", fragmentFile), context, options)); } function mergeFragments(fragments, mergeRenders) { return new Fragment( mergeRenders(fragments.map((f) => f.render)), new ImportMap().mergeWith(...fragments), new Set(fragments.flatMap((f) => [...f.features])) ); } var Fragment = class _Fragment { render; imports; features; constructor(render2, imports, features) { this.render = render2; this.imports = imports ? new ImportMap().mergeWith(imports) : new ImportMap(); this.features = /* @__PURE__ */ new Set([...features ?? []]); } setRender(render2) { this.render = render2; return this; } mapRender(fn) { this.render = fn(this.render); return this; } addImports(module, imports) { this.imports.add(module, imports); return this; } removeImports(module, imports) { this.imports.remove(module, imports); return this; } mergeImportsWith(...others) { this.imports.mergeWith(...others); return this; } addImportAlias(module, name, alias) { this.imports.addAlias(module, name, alias); return this; } addFeatures(features) { const featureArray = typeof features === "string" ? [features] : features; featureArray.forEach((f) => this.features.add(f)); return this; } removeFeatures(features) { const featureArray = typeof features === "string" ? [features] : features; featureArray.forEach((f) => this.features.delete(f)); return this; } hasFeatures(features) { const featureArray = typeof features === "string" ? [features] : features; return featureArray.every((f) => this.features.has(f)); } mergeFeaturesWith(...others) { others.forEach((f) => this.addFeatures([...f.features])); return this; } clone() { return new _Fragment(this.render).mergeImportsWith(this.imports); } toString() { return this.render; } }; // src/fragments/accountFetchHelpers.ts function getAccountFetchHelpersFragment(scope) { const { accountPath, typeManifest: typeManifest2, nameApi, customAccountData } = scope; const accountNode = getLastNodeFromPath(accountPath); const hasCustomData = customAccountData.has(accountNode.name); const accountTypeFragment = hasCustomData ? typeManifest2.strictType.clone() : fragment(nameApi.dataType(accountNode.name)); const decoderFunctionFragment = hasCustomData ? typeManifest2.decoder.clone() : fragment(`${nameApi.decoderFunction(accountNode.name)}()`); return fragmentFromTemplate("accountFetchHelpers.njk", { accountType: accountTypeFragment.render, decodeFunction: nameApi.accountDecodeFunction(accountNode.name), decoderFunction: decoderFunctionFragment.render, fetchAllFunction: nameApi.accountFetchAllFunction(accountNode.name), fetchAllMaybeFunction: nameApi.accountFetchAllMaybeFunction(accountNode.name), fetchFunction: nameApi.accountFetchFunction(accountNode.name), fetchMaybeFunction: nameApi.accountFetchMaybeFunction(accountNode.name) }).mergeImportsWith(accountTypeFragment, decoderFunctionFragment).addImports("solanaAddresses", ["type Address"]).addImports("solanaAccounts", [ "type Account", "assertAccountExists", "assertAccountsExist", "decodeAccount", "type EncodedAccount", "fetchEncodedAccount", "fetchEncodedAccounts", "type FetchAccountConfig", "type FetchAccountsConfig", "type MaybeAccount", "type MaybeEncodedAccount" ]); } // src/fragments/accountPdaHelpers.ts import { isNodeFilter } from "@codama/nodes"; import { findProgramNodeFromPath, getLastNodeFromPath as getLastNodeFromPath2 } from "@codama/visitors-core"; function getAccountPdaHelpersFragment(scope) { const { accountPath, nameApi, linkables, customAccountData, typeManifest: typeManifest2 } = scope; const accountNode = getLastNodeFromPath2(accountPath); const programNode = findProgramNodeFromPath(accountPath); const pdaNode = accountNode.pda ? linkables.get([...accountPath, accountNode.pda]) : void 0; if (!pdaNode) { return fragment(""); } const accountTypeFragment = customAccountData.has(accountNode.name) ? typeManifest2.strictType.clone() : fragment(nameApi.dataType(accountNode.name)); const importFrom = "generatedPdas"; const pdaSeedsType = nameApi.pdaSeedsType(pdaNode.name); const findPdaFunction = nameApi.pdaFindFunction(pdaNode.name); const hasVariableSeeds = pdaNode.seeds.filter(isNodeFilter("variablePdaSeedNode")).length > 0; return fragmentFromTemplate("accountPdaHelpers.njk", { accountType: accountTypeFragment.render, fetchFromSeedsFunction: nameApi.accountFetchFromSeedsFunction(accountNode.name), fetchFunction: nameApi.accountFetchFunction(accountNode.name), fetchMaybeFromSeedsFunction: nameApi.accountFetchMaybeFromSeedsFunction(accountNode.name), fetchMaybeFunction: nameApi.accountFetchMaybeFunction(accountNode.name), findPdaFunction, hasVariableSeeds, pdaSeedsType, program: programNode }).mergeImportsWith(accountTypeFragment).addImports(importFrom, hasVariableSeeds ? [pdaSeedsType, findPdaFunction] : [findPdaFunction]).addImports("solanaAddresses", ["type Address"]).addImports("solanaAccounts", [ "type Account", "assertAccountExists", "type FetchAccountConfig", "type MaybeAccount" ]); } // src/fragments/accountSizeHelpers.ts import { getLastNodeFromPath as getLastNodeFromPath3 } from "@codama/visitors-core"; function getAccountSizeHelpersFragment(scope) { const { accountPath, nameApi } = scope; const accountNode = getLastNodeFromPath3(accountPath); if (accountNode.size == null) { return fragment(""); } return fragmentFromTemplate("accountSizeHelpers.njk", { account: accountNode, getSizeFunction: nameApi.accountGetSizeFunction(accountNode.name) }); } // src/fragments/accountType.ts import { resolveNestedTypeNode } from "@codama/nodes"; import { getLastNodeFromPath as getLastNodeFromPath4 } from "@codama/visitors-core"; // src/fragments/type.ts function getTypeFragment(scope) { const { name, manifest, nameApi, docs = [] } = scope; const typeFragment = fragmentFromTemplate("type.njk", { docs, looseName: nameApi.dataArgsType(name), manifest, strictName: nameApi.dataType(name) }); if (!manifest.isEnum) { typeFragment.mergeImportsWith(manifest.strictType, manifest.looseType); } return typeFragment; } // src/fragments/typeDecoder.ts import { isDataEnum, isNode as isNode3 } from "@codama/nodes"; function getTypeDecoderFragment(scope) { const { name, node, manifest, nameApi, docs = [] } = scope; const decoderType = typeof scope.size === "number" ? "FixedSizeDecoder" : "Decoder"; const useTypeCast = isNode3(node, "enumTypeNode") && isDataEnum(node) && typeof scope.size === "number"; return fragmentFromTemplate("typeDecoder.njk", { decoderFunction: nameApi.decoderFunction(name), decoderType, docs, looseName: nameApi.dataArgsType(name), manifest, strictName: nameApi.dataType(name), useTypeCast }).mergeImportsWith(manifest.decoder).addImports("solanaCodecsCore", `type ${decoderType}`); } // src/fragments/typeEncoder.ts import { isDataEnum as isDataEnum2, isNode as isNode4 } from "@codama/nodes"; function getTypeEncoderFragment(scope) { const { name, node, manifest, nameApi, docs = [] } = scope; const encoderType = typeof scope.size === "number" ? "FixedSizeEncoder" : "Encoder"; const useTypeCast = isNode4(node, "enumTypeNode") && isDataEnum2(node) && typeof scope.size === "number"; return fragmentFromTemplate("typeEncoder.njk", { docs, encoderFunction: nameApi.encoderFunction(name), encoderType, looseName: nameApi.dataArgsType(name), manifest, strictName: nameApi.dataType(name), useTypeCast }).mergeImportsWith(manifest.encoder).addImports("solanaCodecsCore", `type ${encoderType}`); } // src/fragments/typeCodec.ts function getTypeCodecFragment(scope) { const { name, manifest, nameApi } = scope; const codecType = typeof scope.size === "number" ? "FixedSizeCodec" : "Codec"; return mergeFragments( [ getTypeEncoderFragment({ ...scope, docs: scope.encoderDocs }), getTypeDecoderFragment({ ...scope, docs: scope.decoderDocs }), fragmentFromTemplate("typeCodec.njk", { codecFunction: nameApi.codecFunction(name), codecType, decoderFunction: nameApi.decoderFunction(name), docs: scope.codecDocs, encoderFunction: nameApi.encoderFunction(name), looseName: nameApi.dataArgsType(name), manifest, strictName: nameApi.dataType(name) }).addImports("solanaCodecsCore", [`type ${codecType}`, "combineCodec"]) ], (renders) => renders.join("\n\n") ); } // src/fragments/typeWithCodec.ts function getTypeWithCodecFragment(scope) { return mergeFragments( [getTypeFragment({ ...scope, docs: scope.typeDocs }), getTypeCodecFragment(scope)], (renders) => renders.join("\n\n") ); } // src/fragments/accountType.ts function getAccountTypeFragment(scope) { const { accountPath, typeManifest: typeManifest2, nameApi, customAccountData } = scope; const accountNode = getLastNodeFromPath4(accountPath); if (customAccountData.has(accountNode.name)) { return fragment(""); } return getTypeWithCodecFragment({ manifest: typeManifest2, name: accountNode.name, nameApi, node: resolveNestedTypeNode(accountNode.data), size: scope.size }); } // src/fragments/discriminatorCondition.ts import { constantDiscriminatorNode, constantValueNode, constantValueNodeFromBytes, isNode as isNode5, isNodeFilter as isNodeFilter2 } from "@codama/nodes"; import { visit } from "@codama/visitors-core"; import { getBase64Decoder } from "@solana/codecs-strings"; function getDiscriminatorConditionFragment(scope) { return mergeFragments( scope.discriminators.flatMap((discriminator) => { if (isNode5(discriminator, "sizeDiscriminatorNode")) { return [getSizeConditionFragment(discriminator, scope)]; } if (isNode5(discriminator, "constantDiscriminatorNode")) { return [getByteConditionFragment(discriminator, scope)]; } if (isNode5(discriminator, "fieldDiscriminatorNode")) { return [getFieldConditionFragment(discriminator, scope)]; } return []; }), (r) => r.join(" && ") ).mapRender((r) => `if (${r}) { ${scope.ifTrue}; }`); } function getSizeConditionFragment(discriminator, scope) { const { dataName } = scope; return fragment(`${dataName}.length === ${discriminator.size}`); } function getByteConditionFragment(discriminator, scope) { const { dataName, typeManifestVisitor } = scope; const constant = visit(discriminator.constant, typeManifestVisitor).value; return constant.mapRender((r) => `containsBytes(${dataName}, ${r}, ${discriminator.offset})`).addImports("solanaCodecsCore", "containsBytes"); } function getFieldConditionFragment(discriminator, scope) { const field = scope.struct.fields.find((f) => f.name === discriminator.name); if (!field || !field.defaultValue) { throw new Error( `Field discriminator "${discriminator.name}" does not have a matching argument with default value.` ); } if (isNode5(field.type, "arrayTypeNode") && isNode5(field.type.item, "numberTypeNode") && field.type.item.format === "u8" && isNode5(field.type.count, "fixedCountNode") && isNode5(field.defaultValue, "arrayValueNode") && field.defaultValue.items.every(isNodeFilter2("numberValueNode"))) { const base64Bytes = getBase64Decoder().decode( new Uint8Array(field.defaultValue.items.map((node) => node.number)) ); return getByteConditionFragment( constantDiscriminatorNode(constantValueNodeFromBytes("base64", base64Bytes), discriminator.offset), scope ); } return getByteConditionFragment( constantDiscriminatorNode(constantValueNode(field.type, field.defaultValue), discriminator.offset), scope ); } // src/fragments/instructionAccountMeta.ts import { pascalCase as pascalCase2 } from "@codama/nodes"; function getInstructionAccountMetaFragment(instructionAccountNode) { const typeParam = `TAccount${pascalCase2(instructionAccountNode.name)}`; if (instructionAccountNode.isSigner === true && instructionAccountNode.isWritable) { return fragment(`WritableSignerAccount<${typeParam}> & AccountSignerMeta<${typeParam}>`).addImports("solanaInstructions", ["type WritableSignerAccount"]).addImports("solanaSigners", ["type AccountSignerMeta"]); } if (instructionAccountNode.isSigner === true) { return fragment(`ReadonlySignerAccount<${typeParam}> & AccountSignerMeta<${typeParam}>`).addImports("solanaInstructions", ["type ReadonlySignerAccount"]).addImports("solanaSigners", ["type AccountSignerMeta"]); } if (instructionAccountNode.isWritable) { return fragment(`WritableAccount<${typeParam}>`).addImports("solanaInstructions", "type WritableAccount"); } return fragment(`ReadonlyAccount<${typeParam}>`).addImports("solanaInstructions", "type ReadonlyAccount"); } // src/fragments/instructionAccountTypeParam.ts import { pascalCase as pascalCase3 } from "@codama/nodes"; import { findInstructionNodeFromPath, findProgramNodeFromPath as findProgramNodeFromPath2, getLastNodeFromPath as getLastNodeFromPath5 } from "@codama/visitors-core"; function getInstructionAccountTypeParamFragment(scope) { const { instructionAccountPath, allowAccountMeta, linkables } = scope; const instructionAccountNode = getLastNodeFromPath5(instructionAccountPath); const instructionNode = findInstructionNodeFromPath(instructionAccountPath); const programNode = findProgramNodeFromPath2(instructionAccountPath); const typeParam = `TAccount${pascalCase3(instructionAccountNode.name)}`; const accountMeta = allowAccountMeta ? " | AccountMeta<string>" : ""; const imports = new ImportMap(); if (allowAccountMeta) { imports.add("solanaInstructions", "type AccountMeta"); } if (instructionNode.optionalAccountStrategy === "omitted" && instructionAccountNode.isOptional) { return fragment(`${typeParam} extends string${accountMeta} | undefined = undefined`, imports); } const defaultAddress = getDefaultAddress(instructionAccountNode.defaultValue, programNode.publicKey, linkables); return fragment(`${typeParam} extends string${accountMeta} = ${defaultAddress}`, imports); } function getDefaultAddress(defaultValue, programId, linkables) { switch (defaultValue?.kind) { case "publicKeyValueNode": return `"${defaultValue.publicKey}"`; case "programLinkNode": const programNode = linkables.get([defaultValue]); return programNode ? `"${programNode.publicKey}"` : "string"; case "programIdValueNode": return `"${programId}"`; default: return "string"; } } // src/fragments/instructionByteDelta.ts import { assertIsNode, camelCase as camelCase3, isNode as isNode6 } from "@codama/nodes"; import { getLastNodeFromPath as getLastNodeFromPath6 } from "@codama/visitors-core"; function getInstructionByteDeltaFragment(scope) { const { byteDeltas } = getLastNodeFromPath6(scope.instructionPath); const fragments = (byteDeltas ?? []).flatMap((r) => getByteDeltaFragment(r, scope)); if (fragments.length === 0) return fragment(""); return mergeFragments( fragments, (r) => `// Bytes created or reallocated by the instruction. const byteDelta: number = [${r.join(",")}].reduce((a, b) => a + b, 0);` ); } function getByteDeltaFragment(byteDelta, scope) { const bytesFragment = (() => { if (isNode6(byteDelta.value, "numberValueNode")) { return getNumberValueNodeFragment(byteDelta); } if (isNode6(byteDelta.value, "argumentValueNode")) { return getArgumentValueNodeFragment(byteDelta); } if (isNode6(byteDelta.value, "accountLinkNode")) { return getAccountLinkNodeFragment(byteDelta, scope); } if (isNode6(byteDelta.value, "resolverValueNode")) { return getResolverValueNodeFragment(byteDelta, scope); } return null; })(); if (bytesFragment === null) return []; if (byteDelta.withHeader) { bytesFragment.mapRender((r) => `${r} + BASE_ACCOUNT_SIZE`).addImports("solanaAccounts", "BASE_ACCOUNT_SIZE"); } if (byteDelta.subtract) { bytesFragment.mapRender((r) => `- (${r})`); } return [bytesFragment]; } function getNumberValueNodeFragment(byteDelta) { assertIsNode(byteDelta.value, "numberValueNode"); return fragment(byteDelta.value.number.toString()); } function getArgumentValueNodeFragment(byteDelta) { assertIsNode(byteDelta.value, "argumentValueNode"); const argumentName = camelCase3(byteDelta.value.name); return fragment(`Number(args.${argumentName})`); } function getAccountLinkNodeFragment(byteDelta, scope) { assertIsNode(byteDelta.value, "accountLinkNode"); const functionName = scope.nameApi.accountGetSizeFunction(byteDelta.value.name); return fragment(`${functionName}()`).addImports(scope.getImportFrom(byteDelta.value), functionName); } function getResolverValueNodeFragment(byteDelta, scope) { assertIsNode(byteDelta.value, "resolverValueNode"); const isAsync = scope.asyncResolvers.includes(byteDelta.value.name); if (!scope.useAsync && isAsync) return null; const awaitKeyword = scope.useAsync && isAsync ? "await " : ""; const functionName = scope.nameApi.resolverFunction(byteDelta.value.name); return fragment(`${awaitKeyword}${functionName}(resolverScope)`).addImports(scope.getImportFrom(byteDelta.value), functionName).addFeatures(["instruction:resolverScopeVariable"]); } // src/fragments/instructionData.ts import { structTypeNodeFromInstructionArgumentNodes as structTypeNodeFromInstructionArgumentNodes2 } from "@codama/nodes"; import { getLastNodeFromPath as getLastNodeFromPath7 } from "@codama/visitors-core"; function getInstructionDataFragment(scope) { const { instructionPath, dataArgsManifest, nameApi, customInstructionData } = scope; const instructionNode = getLastNodeFromPath7(instructionPath); if (instructionNode.arguments.length === 0 || customInstructionData.has(instructionNode.name)) { return fragment(""); } const instructionDataName = nameApi.instructionDataType(instructionNode.name); return getTypeWithCodecFragment({ manifest: dataArgsManifest, name: instructionDataName, nameApi, node: structTypeNodeFromInstructionArgumentNodes2(instructionNode.arguments), size: scope.size }); } // src/fragments/discriminatorConstants.ts import { camelCase as camelCase4, isNode as isNode7, isNodeFilter as isNodeFilter3, VALUE_NODES } from "@codama/nodes"; import { visit as visit2 } from "@codama/visitors-core"; function getDiscriminatorConstantsFragment(scope) { const fragments = scope.discriminatorNodes.map((node) => getDiscriminatorConstantFragment(node, scope)).filter(Boolean); return mergeFragments(fragments, (r) => r.join("\n\n")); } function getDiscriminatorConstantFragment(discriminatorNode, scope) { switch (discriminatorNode.kind) { case "constantDiscriminatorNode": return getConstantDiscriminatorConstantFragment(discriminatorNode, scope); case "fieldDiscriminatorNode": return getFieldDiscriminatorConstantFragment(discriminatorNode, scope); default: return null; } } function getConstantDiscriminatorConstantFragment(discriminatorNode, scope) { const { discriminatorNodes, typeManifestVisitor, prefix } = scope; const index = discriminatorNodes.filter(isNodeFilter3("constantDiscriminatorNode")).indexOf(discriminatorNode); const suffix = index <= 0 ? "" : `_${index + 1}`; const name = camelCase4(`${prefix}_discriminator${suffix}`); const encoder = visit2(discriminatorNode.constant.type, typeManifestVisitor).encoder; const value = visit2(discriminatorNode.constant.value, typeManifestVisitor).value; return getConstantFragment({ ...scope, encoder, name, value }); } function getFieldDiscriminatorConstantFragment(discriminatorNode, scope) { const { fields, prefix, typeManifestVisitor } = scope; const field = fields.find((f) => f.name === discriminatorNode.name); if (!field || !field.defaultValue || !isNode7(field.defaultValue, VALUE_NODES)) { return null; } const name = camelCase4(`${prefix}_${discriminatorNode.name}`); const encoder = visit2(field.type, typeManifestVisitor).encoder; const value = visit2(field.defaultValue, typeManifestVisitor).value; return getConstantFragment({ ...scope, encoder, name, value }); } function getConstantFragment(scope) { const { encoder, name, nameApi, value } = scope; const constantName = nameApi.constant(name); const constantFunction = nameApi.constantFunction(name); return mergeFragments( [ value.mapRender((r) => `export const ${constantName} = ${r};`), encoder.mapRender((r) => `export function ${constantFunction}() { return ${r}.encode(${constantName}); }`) ], (r) => r.join("\n\n") ); } // src/fragments/instructionExtraArgs.ts import { getLastNodeFromPath as getLastNodeFromPath8 } from "@codama/visitors-core"; function getInstructionExtraArgsFragment(scope) { const { instructionPath, extraArgsManifest, nameApi } = scope; const instructionNode = getLastNodeFromPath8(instructionPath); if ((instructionNode.extraArguments ?? []).length === 0) { return fragment(""); } const instructionExtraName = nameApi.instructionExtraType(instructionNode.name); return fragmentFromTemplate("instructionExtraArgs.njk", { looseName: nameApi.dataArgsType(instructionExtraName), manifest: extraArgsManifest, strictName: nameApi.dataType(instructionExtraName) }).mergeImportsWith(extraArgsManifest.looseType); } // src/fragments/instructionFunction.ts import { camelCase as camelCase9, isNode as isNode12, isNodeFilter as isNodeFilter4, pascalCase as pascalCase5 } from "@codama/nodes"; import { findProgramNodeFromPath as findProgramNodeFromPath3, getLastNodeFromPath as getLastNodeFromPath12 } from "@codama/visitors-core"; // src/fragments/instructionInputResolved.ts import { camelCase as camelCase6, isNode as isNode9, parseOptionalAccountStrategy } from "@codama/nodes"; import { getLastNodeFromPath as getLastNodeFromPath9 } from "@codama/visitors-core"; // src/fragments/instructionInputDefault.ts import { camelCase as camelCase5, isNode as isNode8 } from "@codama/nodes"; import { visit as visit3 } from "@codama/visitors-core"; function getInstructionInputDefaultFragment(scope) { const { input, optionalAccountStrategy, asyncResolvers, useAsync, nameApi, typeManifestVisitor, getImportFrom } = scope; if (!input.defaultValue) { return fragment(""); } if (!useAsync && isAsyncDefaultValue(input.defaultValue, asyncResolvers)) { return fragment(""); } const { defaultValue } = input; const defaultFragment = (renderedValue, isWritable) => { const inputName = camelCase5(input.name); if (input.kind === "instructionAccountNode" && isNode8(defaultValue, "resolverValueNode")) { return fragment(`accounts.${inputName} = { ...accounts.${inputName}, ...${renderedValue} };`); } if (input.kind === "instructionAccountNode" && isWritable === void 0) { return fragment(`accounts.${inputName}.value = ${renderedValue};`); } if (input.kind === "instructionAccountNode") { return fragment( `accounts.${inputName}.value = ${renderedValue}; accounts.${inputName}.isWritable = ${isWritable ? "true" : "false"}` ); } return fragment(`args.${inputName} = ${renderedValue};`); }; switch (defaultValue.kind) { case "accountValueNode": const name = camelCase5(defaultValue.name); if (input.kind === "instructionAccountNode" && input.resolvedIsSigner && !input.isSigner) { return defaultFragment(`expectTransactionSigner(accounts.${name}.value).address`).addImports( "shared", "expectTransactionSigner" ); } if (input.kind === "instructionAccountNode") { return defaultFragment(`expectSome(accounts.${name}.value)`).addImports("shared", "expectSome"); } return defaultFragment(`expectAddress(accounts.${name}.value)`).addImports("shared", "expectAddress"); case "pdaValueNode": if (isNode8(defaultValue.pda, "pdaNode")) { const pdaProgram = defaultValue.pda.programId ? fragment( `'${defaultValue.pda.programId}' as Address<'${defaultValue.pda.programId}'>` ).addImports("solanaAddresses", "type Address") : fragment("programAddress"); const pdaSeeds2 = defaultValue.pda.seeds.flatMap((seed) => { if (isNode8(seed, "constantPdaSeedNode") && isNode8(seed.value, "programIdValueNode")) { return [ fragment(`getAddressEncoder().encode(${pdaProgram.render})`).mergeImportsWith(pdaProgram).addImports("solanaAddresses", "getAddressEncoder") ]; } if (isNode8(seed, "constantPdaSeedNode") && !isNode8(seed.value, "programIdValueNode")) { const typeManifest2 = visit3(seed.type, typeManifestVisitor); const valueManifest2 = visit3(seed.value, typeManifestVisitor); return [ fragment( `${typeManifest2.encoder.render}.encode(${valueManifest2.value.render})` ).mergeImportsWith(typeManifest2.encoder, valueManifest2.value) ]; } if (isNode8(seed, "variablePdaSeedNode")) { const typeManifest2 = visit3(seed.type, typeManifestVisitor); const valueSeed = defaultValue.seeds.find((s) => s.name === seed.name)?.value; if (!valueSeed) return []; if (isNode8(valueSeed, "accountValueNode")) { return [ fragment( `${typeManifest2.encoder.render}.encode(expectAddress(accounts.${camelCase5(valueSeed.name)}.value))` ).mergeImportsWith(typeManifest2.encoder).addImports("shared", "expectAddress") ]; } if (isNode8(valueSeed, "argumentValueNode")) { return [ fragment( `${typeManifest2.encoder.render}.encode(expectSome(args.${camelCase5(valueSeed.name)}))` ).mergeImportsWith(typeManifest2.encoder).addImports("shared", "expectSome") ]; } const valueManifest2 = visit3(valueSeed, typeManifestVisitor); return [ fragment( `${typeManifest2.encoder.render}.encode(${valueManifest2.value.render})` ).mergeImportsWith(typeManifest2.encoder, valueManifest2.value) ]; } return []; }); const pdaStatement = mergeFragments([pdaProgram, ...pdaSeeds2], ([p, ...s]) => { const programAddress2 = p === "programAddress" ? p : `programAddress: ${p}`; return `await getProgramDerivedAddress({ ${programAddress2}, seeds: [${s.join(", ")}] })`; }).addImports("solanaAddresses", "getProgramDerivedAddress"); return defaultFragment(pdaStatement.render).mergeImportsWith(pdaStatement); } const pdaFunction = nameApi.pdaFindFunction(defaultValue.pda.name); const pdaArgs = []; const pdaSeeds = defaultValue.seeds.map((seed) => { if (isNode8(seed.value, "accountValueNode")) { return fragment( `${seed.name}: expectAddress(accounts.${camelCase5(seed.value.name)}.value)` ).addImports("shared", "expectAddress"); } if (isNode8(seed.value, "argumentValueNode")) { return fragment(`${seed.name}: expectSome(args.${camelCase5(seed.value.name)})`).addImports( "shared", "expectSome" ); } return visit3(seed.value, typeManifestVisitor).value.mapRender((r) => `${seed.name}: ${r}`); }); const pdaSeedsFragment = mergeFragments(pdaSeeds, (renders) => renders.join(", ")).mapRender((r) => `{ ${r} }`); if (pdaSeeds.length > 0) { pdaArgs.push(pdaSeedsFragment.render); } return defaultFragment(`await ${pdaFunction}(${pdaArgs.join(", ")})`).mergeImportsWith(pdaSeedsFragment).addImports(getImportFrom(defaultValue.pda), pdaFunction); case "publicKeyValueNode": return defaultFragment(`'${defaultValue.publicKey}' as Address<'${defaultValue.publicKey}'>`).addImports( "solanaAddresses", "type Address" ); case "programLinkNode": const programAddress = nameApi.programAddressConstant(defaultValue.name); return defaultFragment(programAddress, false).addImports(getImportFrom(defaultValue), programAddress); case "programIdValueNode": if (optionalAccountStrategy === "programId" && input.kind === "instructionAccountNode" && input.isOptional) { return fragment(""); } return defaultFragment("programAddress", false); case "identityValueNode": case "payerValueNode": return fragment(""); case "accountBumpValueNode": return defaultFragment( `expectProgramDerivedAddress(accounts.${camelCase5(defaultValue.name)}.value)[1]` ).addImports("shared", "expectProgramDerivedAddress"); case "argumentValueNode": return defaultFragment(`expectSome(args.${camelCase5(defaultValue.name)})`).addImports( "shared", "expectSome" ); case "resolverValueNode": const resolverFunction = nameApi.resolverFunction(defaultValue.name); const resolverAwait = useAsync && asyncResolvers.includes(defaultValue.name) ? "await " : ""; return defaultFragment(`${resolverAwait}${resolverFunction}(resolverScope)`).addImports(getImportFrom(defaultValue), resolverFunction).addFeatures(["instruction:resolverScopeVariable"]); case "conditionalValueNode": const ifTrueRenderer = renderNestedInstructionDefault({ ...scope, defaultValue: defaultValue.ifTrue }); const ifFalseRenderer = renderNestedInstructionDefault({ ...scope, defaultValue: defaultValue.ifFalse }); if (!ifTrueRenderer && !ifFalseRenderer) { return fragment(""); } const conditionalFragment = fragment(""); if (ifTrueRenderer) { conditionalFragment.mergeImportsWith(ifTrueRenderer).mergeFeaturesWith(ifTrueRenderer); } if (ifFalseRenderer) { conditionalFragment.mergeImportsWith(ifFalseRenderer).mergeFeaturesWith(ifFalseRenderer); } const negatedCondition = !ifTrueRenderer; let condition = "true"; if (isNode8(defaultValue.condition, "resolverValueNode")) { const conditionalResolverFunction = nameApi.resolverFunction(defaultValue.condition.name); conditionalFragment.addImports(getImportFrom(defaultValue.condition), conditionalResolverFunction).addFeatures(["instruction:resolverScopeVariable"]); const conditionalResolverAwait = useAsync && asyncResolvers.includes(defaultValue.condition.name) ? "await " : ""; condition = `${conditionalResolverAwait}${conditionalResolverFunction}(resolverScope)`; condition = negatedCondition ? `!${condition}` : condition; } else { const comparedInputName = isNode8(defaultValue.condition, "accountValueNode") ? `accounts.${camelCase5(defaultValue.condition.name)}.value` : `args.${camelCase5(defaultValue.condition.name)}`; if (defaultValue.value) { const comparedValue = visit3(defaultValue.value, typeManifestVisitor).value; conditionalFragment.mergeImportsWith(comparedValue).mergeFeaturesWith(comparedValue); const operator = negatedCondition ? "!==" : "==="; condition = `${comparedInputName} ${operator} ${comparedValue.render}`; } else { condition = negatedCondition ? `!${comparedInputName}` : comparedInputName; } } if (ifTrueRenderer && ifFalseRenderer) { return conditionalFragment.setRender( `if (${condition}) { ${ifTrueRenderer.render} } else { ${ifFalseRenderer.render} }` ); } return conditionalFragment.setRender( `if (${condition}) { ${ifTrueRenderer ? ifTrueRenderer.render : ifFalseRenderer?.render} }` ); default: const valueManifest = visit3(defaultValue, typeManifestVisitor).value; return defaultFragment(valueManifest.render).mergeImportsWith(valueManifest); } } function renderNestedInstructionDefault(scope) { const { input, defaultValue } = scope; if (!defaultValue) return void 0; return getInstructionInputDefaultFragment({ ...scope, input: { ...input, defaultValue } }); } // src/fragments/instructionInputResolved.ts function getInstructionInputResolvedFragment(scope) { const instructionNode = getLastNodeFromPath9(scope.instructionPath); const resolvedInputFragments = scope.resolvedInputs.flatMap((input) => { const inputFragment = getInstructionInputDefaultFragment({ ...scope, input, optionalAccountStrategy: parseOptionalAccountStrategy(instructionNode.optionalAccountStrategy) }); if (!inputFragment.render) return []; const camelName = camelCase6(input.name); return [ inputFragment.mapRender( (r) => isNode9(input, "instructionArgumentNode") ? `if (!args.${camelName}) { ${r} }` : `if (!accounts.${camelName}.value) { ${r} }` ) ]; }); if (resolvedInputFragments.length === 0) { return fragment(""); } return mergeFragments( [fragment("// Resolve default values."), ...resolvedInputFragments], (renders) => renders.join("\n") ); } // src/fragments/instructionInputType.ts import { camelCase as camelCase7, getAllInstructionArguments, isNode as isNode10, parseDocs, pascalCase as pascalCase4 } from "@codama/nodes"; import { getLastNodeFromPath as getLastNodeFromPath10 } from "@codama/visitors-core"; function getInstructionInputTypeFragment(scope) { const { instructionPath, useAsync, nameApi } = scope; const instructionNode = getLastNodeFromPath10(instructionPath); const instructionInputType = useAsync ? nameApi.instructionAsyncInputType(instructionNode.name) : nameApi.instructionSyncInputType(instructionNode.name); const accountsFragment = getAccountsFragment(scope); const [dataArgumentsFragment, customDataArgumentsFragment] = getDataArgumentsFragments(scope); const extraArgumentsFragment = getExtraArgumentsFragment(scope); const remainingAccountsFragment = getRemainingAccountsFragment(instructionNode); return fragmentFromTemplate("instructionInputType.njk", { accountsFragment, customDataArgumentsFragment, dataArgumentsFragment, extraArgumentsFragment, instruction: instructionNode, instructionInputType, remainingAccountsFragment }).mergeImportsWith( accountsFragment, dataArgumentsFragment, customDataArgumentsFragment, extraArgumentsFragment, remainingAccountsFragment ).addImports("solanaAddresses", ["type Address"]); } function getAccountsFragment(scope) { const { instructionPath, resolvedInputs, useAsync, asyncResolvers } = scope; const instructionNode = getLastNodeFromPath10(instructionPath); const fragments = instructionNode.accounts.map((account) => { const resolvedAccount = resolvedInputs.find( (input) => input.kind === "instructionAccountNode" && input.name === account.name ); const hasDefaultValue = !!resolvedAccount.defaultValue && !isNode10(resolvedAccount.defaultValue, ["identityValueNode", "payerValueNode"]) && (useAsync || !isAsyncDefaultValue(resolvedAccount.defaultValue, asyncResolvers)); const accountDocs = parseDocs(account.docs); const docblock = accountDocs.length > 0 ? jsDocblock(accountDocs) : ""; const optionalSign = hasDefaultValue || resolvedAccount.isOptional ? "?" : ""; return getAccountTypeFragment2(resolvedAccount).mapRender( (r) => `${docblock}${camelCase7(account.name)}${optionalSign}: ${r};` ); }); return mergeFragments(fragments, (r) => r.join("\n")); } function getAccountTypeFragment2(account) { const typeParam = `TAccount${pascalCase4(account.name)}`; if (account.isPda && account.isSigner === false) { return fragment(`ProgramDerivedAddress<${typeParam}>`).addImports("solanaAddresses", [ "type ProgramDerivedAddress" ]); } if (account.isPda && account.isSigner === "either") { return fragment(`ProgramDerivedAddress<${typeParam}> | TransactionSigner<${typeParam}>`).addImports("solanaAddresses", ["type ProgramDerivedAddress"]).addImports("solanaSigners", ["type TransactionSigner"]); } if (account.isSigner === "either") { return fragment(`Address<${typeParam}> | TransactionSigner<${typeParam}>`).addImports("solanaAddresses", ["type Address"]).addImports("solanaSigners", ["type TransactionSigner"]); } if (account.isSigner) { return fragment(`TransactionSigner<${typeParam}>`).addImpor