@honeycomb-protocol/solita
Version:
Generates SDK API from solana contract IDL.
371 lines (370 loc) • 16.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Solita = void 0;
const assert_1 = require("assert");
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const prettier_1 = require("prettier");
const paths_1 = require("./paths");
const render_account_1 = require("./render-account");
const render_account_providers_1 = require("./render-account-providers");
const render_errors_1 = require("./render-errors");
const render_instruction_1 = require("./render-instruction");
const render_type_1 = require("./render-type");
const serializers_1 = require("./serializers");
const types_1 = require("./types");
const utils_1 = require("./utils");
__exportStar(require("./types"), exports);
const DEFAULT_FORMAT_OPTS = {
semi: false,
singleQuote: true,
trailingComma: 'es5',
useTabs: false,
tabWidth: 2,
arrowParens: 'always',
printWidth: 80,
parser: 'typescript',
};
class Solita {
constructor(idl, { formatCode = false, formatOpts = {}, prependGeneratedWarning = true, typeAliases = {}, serializers = {}, projectRoot = process.cwd(), anchorRemainingAccounts, } = {}) {
this.idl = idl;
this.resolveFieldType = (typeName) => {
var _a, _b;
for (const acc of (_a = this.idl.accounts) !== null && _a !== void 0 ? _a : []) {
if (acc.name === typeName)
return acc.type;
}
for (const def of (_b = this.idl.types) !== null && _b !== void 0 ? _b : []) {
if (def.name === typeName)
return def.type;
}
return null;
};
this.projectRoot = projectRoot;
this.formatCode = formatCode;
this.formatOpts = { ...DEFAULT_FORMAT_OPTS, ...formatOpts };
this.prependGeneratedWarning = prependGeneratedWarning;
this.accountsHaveImplicitDiscriminator = !(0, types_1.isShankIdl)(idl);
this.typeAliases = new Map(Object.entries(typeAliases));
this.serializers = serializers_1.CustomSerializers.create(this.projectRoot, new Map(Object.entries(serializers)));
this.hasInstructions = idl.instructions.length > 0;
// Unless remaining accounts are specifically turned off, we support them
// for anchor programs
this.anchorRemainingAccounts = anchorRemainingAccounts !== null && anchorRemainingAccounts !== void 0 ? anchorRemainingAccounts : (0, types_1.isAnchorIdl)(idl);
}
// -----------------
// Extract
// -----------------
accountFilesByType() {
var _a, _b;
(0, assert_1.strict)(this.paths != null, 'should have set paths');
return new Map((_b = (_a = this.idl.accounts) === null || _a === void 0 ? void 0 : _a.map((x) => [
x.name,
this.paths.accountFile(x.name),
])) !== null && _b !== void 0 ? _b : []);
}
customFilesByType() {
var _a, _b;
(0, assert_1.strict)(this.paths != null, 'should have set paths');
return new Map((_b = (_a = this.idl.types) === null || _a === void 0 ? void 0 : _a.map((x) => [x.name, this.paths.typeFile(x.name)])) !== null && _b !== void 0 ? _b : []);
}
externalPackagesByType() {
(0, assert_1.strict)(this.paths != null, 'should have set paths');
return new Map(this.idl.externalTypes
? Object.entries(this.idl.externalTypes).map(([name, pkg]) => [
name,
pkg,
])
: []);
}
// -----------------
// Render
// -----------------
renderCode() {
var _a, _b, _c, _d, _e, _f;
(0, assert_1.strict)(this.paths != null, 'should have set paths');
const programId = this.idl.metadata.address;
const fixableTypes = new Set();
const accountFiles = this.accountFilesByType();
const customFiles = this.customFilesByType();
const externalPackages = this.externalPackagesByType();
function forceFixable(ty) {
if ((0, types_1.isIdlTypeDefined)(ty) && fixableTypes.has(ty.defined.name)) {
return true;
}
if ((0, types_1.isIdlTypeGeneric)(ty)) {
return true;
}
return false;
}
// NOTE: we render types first in order to know which ones are 'fixable' by
// the time we render accounts and instructions
// However since types may depend on other types we obtain this info in 2 passes.
// -----------------
// Types
// -----------------
const types = {};
(0, utils_1.logDebug)('Rendering %d types', (_b = (_a = this.idl.types) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0);
if (this.idl.types != null) {
for (let ty of this.idl.types) {
// Here we detect if the type itself is fixable solely based on its
// primitive field types
let isFixable = (0, render_type_1.determineTypeIsFixable)(ty, this.paths.typesDir, accountFiles, customFiles, externalPackages);
if (isFixable) {
fixableTypes.add(ty.name);
}
}
for (const ty of this.idl.types) {
(0, utils_1.logDebug)(`Rendering type ${ty.name}`);
(0, utils_1.logTrace)('kind: %s', ty.type.kind);
if ((0, types_1.isIdlFieldsType)(ty.type)) {
(0, utils_1.logTrace)('fields: %O', ty.type.fields);
}
else {
if ((0, types_1.isIdlTypeEnum)(ty.type)) {
(0, utils_1.logTrace)('variants: %O', ty.type.variants);
}
}
let { code, isFixable } = (0, render_type_1.renderType)(ty, this.paths.typesDir, accountFiles, customFiles, externalPackages, this.typeAliases, forceFixable);
// If the type by itself does not need to be fixable, here we detect if
// it needs to be fixable due to including a fixable type
if (isFixable) {
fixableTypes.add(ty.name);
}
if (this.prependGeneratedWarning) {
code = (0, utils_1.prependGeneratedWarning)(code);
}
if (this.formatCode) {
try {
code = (0, prettier_1.format)(code, this.formatOpts);
}
catch (err) {
(0, utils_1.logError)(`Failed to format ${ty.name} instruction`);
(0, utils_1.logError)(err);
}
}
types[ty.name] = code;
}
}
// -----------------
// Instructions
// -----------------
const instructions = {};
for (const ix of this.idl.instructions) {
(0, utils_1.logDebug)(`Rendering instruction ${ix.name}`);
(0, utils_1.logTrace)('args: %O', ix.args);
(0, utils_1.logTrace)('accounts: %O', ix.accounts);
let code = (0, render_instruction_1.renderInstruction)(ix, this.paths.instructionsDir, programId, accountFiles, customFiles, externalPackages, this.typeAliases, forceFixable, this.anchorRemainingAccounts);
if (this.prependGeneratedWarning) {
code = (0, utils_1.prependGeneratedWarning)(code);
}
if (this.formatCode) {
try {
code = (0, prettier_1.format)(code, this.formatOpts);
}
catch (err) {
(0, utils_1.logError)(`Failed to format ${ix.name} instruction`);
(0, utils_1.logError)(err);
}
}
instructions[ix.name] = code;
}
// -----------------
// Accounts
// -----------------
const accounts = {};
for (const account of (_c = this.idl.accounts) !== null && _c !== void 0 ? _c : []) {
(0, utils_1.logDebug)(`Rendering account ${account.name}`);
(0, utils_1.logTrace)('type: %O', account.type);
let code = (0, render_account_1.renderAccount)(account, this.paths.accountsDir, accountFiles, customFiles, externalPackages, this.typeAliases, this.serializers, forceFixable, programId, this.resolveFieldType, this.accountsHaveImplicitDiscriminator);
if (this.prependGeneratedWarning) {
code = (0, utils_1.prependGeneratedWarning)(code);
}
if (this.formatCode) {
try {
code = (0, prettier_1.format)(code, this.formatOpts);
}
catch (err) {
(0, utils_1.logError)(`Failed to format ${account.name} account`);
(0, utils_1.logError)(err);
}
}
accounts[account.name] = code;
}
// -----------------
// Errors
// -----------------
(0, utils_1.logDebug)('Rendering %d errors', (_e = (_d = this.idl.errors) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0);
let errors = (0, render_errors_1.renderErrors)((_f = this.idl.errors) !== null && _f !== void 0 ? _f : []);
if (errors != null && this.prependGeneratedWarning) {
errors = (0, utils_1.prependGeneratedWarning)(errors);
}
if (errors != null && this.formatCode) {
try {
errors = (0, prettier_1.format)(errors, this.formatOpts);
}
catch (err) {
(0, utils_1.logError)(`Failed to format errors`);
(0, utils_1.logError)(err);
}
}
return { instructions, accounts, types, errors };
}
async renderAndWriteTo(outputDir) {
this.paths = new paths_1.Paths(outputDir);
const { instructions, accounts, types, errors } = this.renderCode();
const reexports = [];
if (this.hasInstructions) {
reexports.push('instructions');
await this.writeInstructions(instructions);
}
if (Object.keys(accounts).length !== 0) {
reexports.push('accounts');
await this.writeAccounts(accounts);
}
if (Object.keys(types).length !== 0) {
reexports.push('types');
await this.writeTypes(types);
}
if (errors != null) {
reexports.push('errors');
await this.writeErrors(errors);
}
await this.writeMainIndex(reexports);
}
// -----------------
// Instructions
// -----------------
async writeInstructions(instructions) {
(0, assert_1.strict)(this.paths != null, 'should have set paths');
await (0, utils_1.prepareTargetDir)(this.paths.instructionsDir);
(0, utils_1.logInfo)('Writing instructions to directory: %s', this.paths.relInstructionsDir);
for (const [name, code] of Object.entries(instructions)) {
(0, utils_1.logDebug)('Writing instruction: %s', name);
await fs_1.promises.writeFile(this.paths.instructionFile(name), code, 'utf8');
}
(0, utils_1.logDebug)('Writing index.ts exporting all instructions');
const indexCode = this.renderImportIndex(Object.keys(instructions).sort(), 'instructions');
await fs_1.promises.writeFile(this.paths.instructionFile('index'), indexCode, 'utf8');
}
// -----------------
// Accounts
// -----------------
async writeAccounts(accounts) {
(0, assert_1.strict)(this.paths != null, 'should have set paths');
await (0, utils_1.prepareTargetDir)(this.paths.accountsDir);
(0, utils_1.logInfo)('Writing accounts to directory: %s', this.paths.relAccountsDir);
for (const [name, code] of Object.entries(accounts)) {
(0, utils_1.logDebug)('Writing account: %s', name);
await fs_1.promises.writeFile(this.paths.accountFile(name), code, 'utf8');
}
(0, utils_1.logDebug)('Writing index.ts exporting all accounts');
const accountProvidersCode = (0, render_account_providers_1.renderAccountProviders)(this.idl.accounts);
const indexCode = this.renderImportIndex(Object.keys(accounts).sort(), 'accounts', accountProvidersCode);
await fs_1.promises.writeFile(this.paths.accountFile('index'), indexCode, 'utf8');
}
// -----------------
// Types
// -----------------
async writeTypes(types) {
(0, assert_1.strict)(this.paths != null, 'should have set paths');
await (0, utils_1.prepareTargetDir)(this.paths.typesDir);
(0, utils_1.logInfo)('Writing types to directory: %s', this.paths.relTypesDir);
for (const [name, code] of Object.entries(types)) {
(0, utils_1.logDebug)('Writing type: %s', name);
await fs_1.promises.writeFile(this.paths.typeFile(name), code, 'utf8');
}
(0, utils_1.logDebug)('Writing index.ts exporting all types');
const reexports = Object.keys(types);
// NOTE: this allows account types to be referenced via `defined.<AccountName>`, however
// it would break if we have an account used that way, but no types
// If that occurs we need to generate the `types/index.ts` just reexporting accounts
const indexCode = this.renderImportIndex(reexports.sort(), 'types');
await fs_1.promises.writeFile(this.paths.typeFile('index'), indexCode, 'utf8');
}
// -----------------
// Errors
// -----------------
async writeErrors(errorsCode) {
(0, assert_1.strict)(this.paths != null, 'should have set paths');
await (0, utils_1.prepareTargetDir)(this.paths.errorsDir);
(0, utils_1.logInfo)('Writing errors to directory: %s', this.paths.relErrorsDir);
(0, utils_1.logDebug)('Writing index.ts containing all errors');
await fs_1.promises.writeFile(this.paths.errorFile('index'), errorsCode, 'utf8');
}
// -----------------
// Main Index File
// -----------------
async writeMainIndex(reexports) {
(0, assert_1.strict)(this.paths != null, 'should have set paths');
const programAddress = this.idl.metadata.address;
const reexportCode = this.renderImportIndex(reexports.sort(), 'main');
const imports = `import { PublicKey } from '${types_1.SOLANA_WEB3_PACKAGE}'`;
const programIdConsts = `
/**
* Program address
*
* @category constants
* @category generated
*/
export const PROGRAM_ADDRESS = '${programAddress}'
/**
* Program public key
*
* @category constants
* @category generated
*/
export const PROGRAM_ID = new PublicKey(PROGRAM_ADDRESS)
`;
let code = `
${imports}
${reexportCode}
${programIdConsts}
`.trim();
if (this.formatCode) {
try {
code = (0, prettier_1.format)(code, this.formatOpts);
}
catch (err) {
(0, utils_1.logError)(`Failed to format mainIndex`);
(0, utils_1.logError)(err);
}
}
await fs_1.promises.writeFile(path_1.default.join(this.paths.root, `index.ts`), code, 'utf8');
}
renderImportIndex(modules, label, extraContent) {
let code = modules.map((x) => `export * from './${x}';`).join('\n');
if (extraContent != null) {
code += `\n\n${extraContent}`;
}
if (this.formatCode) {
try {
code = (0, prettier_1.format)(code, this.formatOpts);
}
catch (err) {
(0, utils_1.logError)(`Failed to format ${label} imports`);
(0, utils_1.logError)(err);
}
}
return code;
}
}
exports.Solita = Solita;
//# sourceMappingURL=solita.js.map