@eliseev_s/tolk-tlb-transpiler
Version:
Transpile Tolk structs to TLB definitions and generate TypeScript wrappers for TON blockchain smart contracts
827 lines • 34.7 kB
JavaScript
"use strict";
// ============================================================================
// TypeScript Wrapper Generator - Generate Smart Contract Wrapper Classes
// ============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.WrapperGenerator = void 0;
const tlb_codegen_1 = require("@ton-community/tlb-codegen");
class WrapperGenerator {
config;
additionalImports = [];
utilityTypes = [];
wrapperClass = '';
typescriptCode = '';
constructor(config) {
this.config = config;
}
/**
* Create and initialize a ready-to-use WrapperGenerator instance
*
* @param config - Configuration for the wrapper generator
* @returns Promise resolving to initialized WrapperGenerator instance
*/
static async create(config) {
const generator = new WrapperGenerator(config);
await generator.initialize();
generator.generateImports().generateUtilityTypes().generateWrapperClass();
return generator;
}
/**
* Initialize wrapper generator with TypeScript code generation
*/
async initialize() {
// Generate TypeScript code from TLB definitions
const commonTLB = `// Common TLB definitions
bool_false$0 = Bool;
bool_true$1 = Bool;
nothing$0 {X:Type} = Maybe X;
just$1 {X:Type} value:X = Maybe X;
left$0 {X:Type} {Y:Type} value:X = Either X Y;
right$1 {X:Type} {Y:Type} value:Y = Either X Y;
pair$_ {X:Type} {Y:Type} first:X second:Y = Both X Y;
`;
const tlbString = this.formatTlbDefinitions();
this.typescriptCode = await (0, tlb_codegen_1.generateCodeFromData)(commonTLB + tlbString, 'typescript');
}
/**
* Format TLB definitions into a readable string format
*/
formatTlbDefinitions() {
const output = [];
output.push('// ============================================================================');
output.push(`// TLB Schema for ${this.config.contractName}`);
output.push('// ============================================================================');
output.push('');
for (const definition of this.config.tlbDefinitions) {
output.push(`// ${definition.name}`);
for (const constructor of definition.statement) {
output.push(constructor);
}
output.push('');
}
return output.join('\n');
}
/**
* Generate additional imports needed for wrapper
*/
generateImports() {
this.additionalImports = [
"import { Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';",
];
return this;
}
/**
* Generate utility types for send methods
*/
generateUtilityTypes() {
const { internalMessageType, structures, typeAliases } = this.config;
// Extract send methods to generate types for them
const sendMethods = WrapperGenerator.extractSendMethods(internalMessageType, structures, typeAliases);
this.utilityTypes = [
"// Utility type to exclude 'kind' field from send method parameters",
"type OmitKind<T> = T extends { kind: any } ? Omit<T, 'kind'> : T;",
'',
'// Body types for send methods',
...sendMethods.map((method) => `export type ${method.messageType}Body = OmitKind<${method.messageType}>;`),
];
return this;
}
/**
* Generate wrapper class
*/
generateWrapperClass() {
const { contractName, storageType, internalMessageType, getMethods, structures, typeAliases, } = this.config;
// Extract send methods from internal message union
const sendMethods = WrapperGenerator.extractSendMethods(internalMessageType, structures, typeAliases);
const parts = [
WrapperGenerator.generateClassDeclaration(contractName),
WrapperGenerator.generateCreateFromAddress(contractName),
WrapperGenerator.generateCreateFromConfig(storageType, contractName),
WrapperGenerator.generateSendDeploy(),
...sendMethods.map((method) => WrapperGenerator.generateCreateBodyMethod(method)),
...sendMethods.map((method) => WrapperGenerator.generateSendMethod(method, contractName)),
...getMethods.map((method) => WrapperGenerator.generateGetMethod(method, structures)),
WrapperGenerator.generateClassEnd(),
];
this.wrapperClass = parts.join('\n\n');
return this;
}
/**
* Render the complete wrapper code
*/
render() {
// Parse TypeScript code to find import section
const lines = this.typescriptCode.split('\n');
const importEndIndex = lines.findIndex((line) => line.trim() === '' &&
lines.slice(0, lines.indexOf(line)).some((l) => l.startsWith('import')));
let fullCode;
if (importEndIndex > 0) {
// Check if beginCell is already imported
const existingImports = lines.slice(0, importEndIndex).join('\n');
const hasBeginCell = existingImports.includes('beginCell');
// Filter out beginCell from additional imports if it's already imported
const filteredImports = this.additionalImports.map((importLine) => {
if (hasBeginCell && importLine.includes('beginCell')) {
return importLine
.replace(/,\s*beginCell\s*/, '')
.replace(/beginCell\s*,\s*/, '');
}
return importLine;
});
// Insert additional imports after existing imports
lines.splice(importEndIndex, 0, ...filteredImports);
const modifiedTypescriptCode = lines.join('\n');
// Combine with utility types and wrapper class
const parts = [
modifiedTypescriptCode,
this.utilityTypes.join('\n'),
this.wrapperClass,
].filter((part) => part.trim());
fullCode = parts.join('\n\n');
}
else {
// Fallback: combine all parts
const parts = [
this.typescriptCode,
this.additionalImports.join('\n'),
this.utilityTypes.join('\n'),
this.wrapperClass,
].filter((part) => part.trim());
fullCode = parts.join('\n\n');
}
return fullCode;
}
/**
* Get the generated TypeScript code
*/
getTypescriptCode() {
return this.typescriptCode;
}
/**
* Get the generated wrapper class code
*/
getWrapperCode() {
return this.wrapperClass;
}
/**
* Get the formatted TLB definitions
*/
getTlbString() {
return this.formatTlbDefinitions();
}
/**
* Generate additional imports needed for wrapper (avoiding duplicates)
*/
static generateAdditionalImports() {
// Only add imports that are not already present in TLB codegen output
return `import { Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';`;
}
/**
* Generate utility types for send methods
*/
static generateUtilityTypes() {
return `// Utility type to exclude 'kind' field from send method parameters
type OmitKind<T> = T extends { kind: any } ? Omit<T, 'kind'> : T;`;
}
/**
* Generate class declaration
*/
static generateClassDeclaration(contractName) {
return `export class ${contractName} implements Contract {
constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {}`;
}
/**
* Generate createFromAddress method
*/
static generateCreateFromAddress(contractName) {
return ` static createFromAddress(address: Address) {
return new ${contractName}(address);
}`;
}
/**
* Generate createFromConfig method
*/
static generateCreateFromConfig(storageType, contractName) {
return ` static createFromConfig(storage: ${storageType}, code: Cell, workchain = 0) {
const data = beginCell().store(store${storageType}(storage)).endCell();
const init = { code, data };
return new ${contractName}(contractAddress(workchain, init), init);
}`;
}
/**
* Generate sendDeploy method
*/
static generateSendDeploy() {
return ` async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
await provider.internal(via, {
value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell().endCell(),
});
}`;
}
/**
* Generate send method for internal message
*/
static generateSendMethod(method, contractName) {
const bodyTypeName = `${method.messageType}Body`;
const createBodyMethodName = `create${method.messageType}Body`;
return ` async ${method.name}(
provider: ContractProvider,
via: Sender,
value: bigint,
params: ${bodyTypeName}
) {
await provider.internal(via, {
value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: ${contractName}.${createBodyMethodName}(params),
});
}`;
}
/**
* Generate static create body method for internal message
*/
static generateCreateBodyMethod(method) {
const bodyTypeName = `${method.messageType}Body`;
const createBodyMethodName = `create${method.messageType}Body`;
return ` static ${createBodyMethodName}(params: ${bodyTypeName}) {
return beginCell().store(store${method.messageType}({ ...params, kind: '${method.messageType}' })).endCell();
}`;
}
/**
* Generate get method
*/
static generateGetMethod(method, structures) {
// Normalize the method name (convert snake_case to camelCase)
const normalizedName = WrapperGenerator.normalizeMethodName(method.name);
// If the original method already starts with 'get', don't add another 'get' prefix
const methodName = normalizedName.startsWith('get')
? normalizedName
: `get${WrapperGenerator.capitalize(normalizedName)}`;
const returnType = WrapperGenerator.tolkTypeToTSType(method.returnType);
let parameters = '';
let args = '';
if (method.parameters.length > 0) {
const paramStrings = method.parameters.map((param) => `${param.name}: ${WrapperGenerator.tolkTypeToTSType(param.type)}`);
parameters = `, ${paramStrings.join(', ')}`;
const argStrings = method.parameters.map((param) => {
return WrapperGenerator.generateStackArgument(param.type, param.name);
});
args = `, [${argStrings.join(',\n ')}]`;
}
else {
// For methods without parameters, pass empty array
args = ', []';
}
return ` async ${methodName}(provider: ContractProvider${parameters}): Promise<${returnType}> {
const { stack } = await provider.get('${method.name}'${args});
return ${WrapperGenerator.generateStackReader(method.returnType, structures)};
}`;
}
/**
* Generate stack argument for get method parameter
*/
static generateStackArgument(type, paramName) {
const paramType = WrapperGenerator.tolkTypeToTSType(type);
if (paramType === 'Address') {
return `{
type: 'cell',
cell: beginCell().storeAddress(${paramName}).endCell(),
}`;
}
else if (paramType === 'bigint') {
return `{
type: 'int',
value: ${paramName},
}`;
}
else if (paramType === 'boolean') {
return `{
type: 'int',
value: ${paramName} ? 1n : 0n,
}`;
}
else if (paramType === 'Cell') {
// Handle cell parameter - just pass the cell directly
return `{
type: 'cell',
cell: ${paramName},
}`;
}
else if (paramType === 'Cell | null') {
// Handle dict type
return `${paramName} === null ? {
type: 'null',
} : {
type: 'cell',
cell: ${paramName},
}`;
}
else if (type.kind === 'primitive') {
switch (type.name) {
case 'int':
return `{
type: 'cell',
cell: beginCell().storeInt(${paramName}, 257).endCell(),
}`;
case 'uint':
return `{
type: 'cell',
cell: beginCell().storeUint(${paramName}, 256).endCell(),
}`;
case 'bool':
return `{
type: 'cell',
cell: beginCell().storeUint(${paramName} ? 1 : 0, 1).endCell(),
}`;
case 'address':
return `{
type: 'cell',
cell: beginCell().storeAddress(${paramName}).endCell(),
}`;
case 'coins':
return `{
type: 'cell',
cell: beginCell().storeCoins(${paramName}).endCell(),
}`;
case 'cell':
// This case should not be reached since Cell parameters are handled above
return `{
type: 'cell',
cell: ${paramName},
}`;
case 'slice':
return `{
type: 'slice',
cell: ${paramName},
}`;
default:
return `{
type: 'cell',
cell: beginCell().storeRef(${paramName}).endCell(),
}`;
}
}
else if (type.kind === 'int') {
return `{
type: 'cell',
cell: beginCell().storeInt(${paramName}, ${type.width}).endCell(),
}`;
}
else if (type.kind === 'uint') {
return `{
type: 'cell',
cell: beginCell().storeUint(${paramName}, ${type.width}).endCell(),
}`;
}
else if (type.kind === 'type_identifier') {
// Handle common type identifiers that are parsed as type_identifier instead of primitive
switch (type.name) {
case 'int':
return `{
type: 'cell',
cell: beginCell().storeInt(${paramName}, 257).endCell(),
}`;
case 'uint':
return `{
type: 'cell',
cell: beginCell().storeUint(${paramName}, 256).endCell(),
}`;
case 'bool':
return `{
type: 'cell',
cell: beginCell().storeUint(${paramName} ? 1 : 0, 1).endCell(),
}`;
case 'address':
return `{
type: 'cell',
cell: beginCell().storeAddress(${paramName}).endCell(),
}`;
case 'coins':
return `{
type: 'cell',
cell: beginCell().storeCoins(${paramName}).endCell(),
}`;
case 'cell':
return `{
type: 'cell',
cell: ${paramName},
}`;
case 'slice':
return `{
type: 'slice',
cell: ${paramName},
}`;
case 'dict':
return `${paramName} === null ? {
type: 'null',
} : {
type: 'cell',
cell: ${paramName},
}`;
default:
// For custom types, use the appropriate store function
return `{
type: 'cell',
cell: beginCell().store(store${WrapperGenerator.tolkTypeToStoreName(type)}(${paramName})).endCell(),
}`;
}
}
else {
// For other custom types, use the appropriate store function
return `{
type: 'cell',
cell: beginCell().store(store${WrapperGenerator.tolkTypeToStoreName(type)}(${paramName})).endCell(),
}`;
}
}
/**
* Generate stack reader for return type
*/
static generateStackReader(returnType, structures) {
switch (returnType.kind) {
case 'primitive':
switch (returnType.name) {
case 'void':
return 'undefined';
case 'bool':
return 'stack.readBoolean()';
case 'int':
case 'uint':
case 'coins':
return 'stack.readBigNumber()';
case 'address':
return 'stack.readAddress()';
case 'cell':
case 'slice':
return 'stack.readCell()';
default:
return 'stack.readCell()';
}
case 'int':
case 'uint':
return 'stack.readBigNumber()';
case 'dict':
return 'stack.readCellOpt()';
case 'special':
switch (returnType.name) {
default:
return 'stack.readCell()';
}
case 'type_identifier':
// Handle void type
if (returnType.name === 'void') {
return 'undefined';
}
// Handle Bool type specifically
if (returnType.name === 'Bool') {
return '{ kind: "Bool", value: stack.readBoolean() }';
}
// Handle common type identifiers
switch (returnType.name) {
case 'int':
return 'stack.readBigNumber()';
case 'uint':
return 'stack.readBigNumber()';
case 'bool':
return 'stack.readBoolean()';
case 'address':
return 'stack.readAddress()';
case 'cell':
return 'stack.readCell()';
case 'coins':
return 'stack.readBigNumber()';
case 'slice':
return 'stack.readCell()';
default:
// Check if this is a struct type
const struct = structures.find((s) => s.name === returnType.name);
if (struct) {
return WrapperGenerator.generateStackReaderForStruct(struct, structures);
}
// For other custom types, assume they need cell parsing
return 'stack.readCell()';
}
case 'tensor':
// Handle tensor returns as tuple - read each element from stack
const tensorReaders = returnType.items.map((item) => WrapperGenerator.generateStackReader(item, structures));
return `[${tensorReaders.join(', ')}]`;
case 'tuple':
// Handle tuple returns as tuple - read each element from stack
const tupleReaders = returnType.items.map((item) => WrapperGenerator.generateStackReader(item, structures));
return `[${tupleReaders.join(', ')}]`;
case 'nullable':
// Handle nullable returns using readCellOpt
const innerReader = WrapperGenerator.generateStackReader(returnType.inner, structures);
return `(() => { const cell = stack.readCellOpt(); return cell === null ? null : ${innerReader.replace('stack.', 'cell.beginParse().')}; })()`;
default:
return 'stack.readCell()';
}
}
/**
* Generate stack reader for struct type
*/
static generateStackReaderForStruct(struct, structures) {
// Generate the field readers
const fieldReaders = struct.fields.map((field) => {
const reader = WrapperGenerator.generateStructFieldStackReader(field.type, structures);
return `${field.name}: ${reader}`;
});
// Create the object with kind field
return `{
kind: '${struct.name}',
${fieldReaders.join(',\n ')}
}`;
}
/**
* Generate stack reader for struct field type (handles special cases for struct compatibility)
*/
static generateStructFieldStackReader(fieldType, structures) {
switch (fieldType.kind) {
case 'primitive':
switch (fieldType.name) {
case 'bool':
// Bool struct type expects an object with kind and value
return '{ kind: "Bool", value: stack.readBoolean() }';
case 'int':
case 'uint':
// For general int/uint, use bigint
return 'stack.readBigNumber()';
case 'coins':
return 'stack.readBigNumber()';
case 'address':
return 'stack.readAddress()';
case 'cell':
case 'slice':
return 'stack.readCell()';
default:
return 'stack.readCell()';
}
case 'uint':
// For specific uint types, uint32 and smaller should be number, larger should be bigint
if (fieldType.width <= 32) {
return 'stack.readNumber()';
}
else {
return 'stack.readBigNumber()';
}
case 'int':
// For specific int types, int32 and smaller should be number, larger should be bigint
if (fieldType.width <= 32) {
return 'stack.readNumber()';
}
else {
return 'stack.readBigNumber()';
}
case 'dict':
// Dict should be Maybe<Cell> - convert null to Maybe_nothing
return '(() => { const cell = stack.readCellOpt(); return cell === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: cell }; })()';
case 'nullable':
// Handle nullable types using loadMaybe
return WrapperGenerator.generateNullableFieldReader(fieldType.inner, structures);
case 'type_identifier':
// Handle common type identifiers
switch (fieldType.name) {
case 'int':
case 'uint':
case 'coins':
return 'stack.readBigNumber()';
case 'bool':
return '{ kind: "Bool", value: stack.readBoolean() }';
case 'address':
return 'stack.readAddress()';
case 'cell':
case 'slice':
return 'stack.readCell()';
default:
// Check if this is a struct type
const struct = structures.find((s) => s.name === fieldType.name);
if (struct) {
return WrapperGenerator.generateStackReaderForStruct(struct, structures);
}
return 'stack.readCell()';
}
default:
return WrapperGenerator.generateStackReader(fieldType, structures);
}
}
/**
* Generate reader for nullable field using loadMaybe
*/
static generateNullableFieldReader(innerType, structures) {
switch (innerType.kind) {
case 'primitive':
switch (innerType.name) {
case 'bool':
return '(() => { const val = stack.readBooleanOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: { kind: "Bool", value: val } }; })()';
case 'int':
case 'uint':
case 'coins':
return '(() => { const val = stack.readBigNumberOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
case 'address':
return '(() => { const val = stack.readAddressOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
case 'cell':
case 'slice':
return '(() => { const val = stack.readCellOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
default:
return '(() => { const val = stack.readCellOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
}
case 'uint':
if (innerType.width <= 32) {
return '(() => { const val = stack.readNumberOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
}
else {
return '(() => { const val = stack.readBigNumberOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
}
case 'int':
if (innerType.width <= 32) {
return '(() => { const val = stack.readNumberOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
}
else {
return '(() => { const val = stack.readBigNumberOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
}
case 'type_identifier':
// Handle common type identifiers
switch (innerType.name) {
case 'int':
case 'uint':
case 'coins':
return '(() => { const val = stack.readBigNumberOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
case 'bool':
return '(() => { const val = stack.readBooleanOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: { kind: "Bool", value: val } }; })()';
case 'address':
return '(() => { const val = stack.readAddressOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
case 'cell':
case 'slice':
return '(() => { const val = stack.readCellOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
default:
// Check if this is a struct type
const struct = structures.find((s) => s.name === innerType.name);
if (struct) {
return ('(() => { const val = stack.readCellOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: load' +
innerType.name +
'(val.beginParse()) }; })()');
}
return '(() => { const val = stack.readCellOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
}
default:
return '(() => { const val = stack.readCellOpt(); return val === null ? { kind: "Maybe_nothing" } : { kind: "Maybe_just", value: val }; })()';
}
}
/**
* Convert Tolk type to TypeScript type
*/
static tolkTypeToTSType(type) {
switch (type.kind) {
case 'primitive':
switch (type.name) {
case 'void':
return 'void';
case 'bool':
return 'boolean';
case 'int':
case 'uint':
case 'coins':
return 'bigint';
case 'address':
return 'Address';
case 'cell':
case 'slice':
case 'builder':
return 'Cell';
default:
return 'Cell';
}
case 'int':
case 'uint':
return 'bigint';
case 'type_identifier':
// Handle void type
if (type.name === 'void') {
return 'void';
}
// Handle common type identifiers that should be mapped to TypeScript types
switch (type.name) {
case 'int':
case 'uint':
case 'coins':
return 'bigint';
case 'bool':
return 'boolean';
case 'address':
return 'Address';
case 'cell':
case 'slice':
return 'Cell';
default:
return type.name;
}
case 'dict':
return 'Cell | null';
case 'special':
switch (type.name) {
default:
return 'Cell';
}
case 'nullable':
return `${WrapperGenerator.tolkTypeToTSType(type.inner)} | null`;
case 'tuple':
if (type.items.length === 0) {
return 'void';
}
const itemTypes = type.items.map((item) => WrapperGenerator.tolkTypeToTSType(item));
return `[${itemTypes.join(', ')}]`;
case 'tensor':
// Tensor types should also be represented as arrays in TypeScript, not objects
if (type.items.length === 0) {
return 'void';
}
const tensorItems = type.items.map((item) => WrapperGenerator.tolkTypeToTSType(item));
return `[${tensorItems.join(', ')}]`;
default:
return 'Cell';
}
}
/**
* Convert Tolk type to store function name
*/
static tolkTypeToStoreName(type) {
if (type.kind === 'type_identifier') {
return type.name;
}
return 'Cell';
}
/**
* Extract send methods from internal message union type name
*/
static extractSendMethods(internalMessageType, structures, typeAliases) {
// Find the type alias for the internal message type
const unionTypeAlias = typeAliases.find((alias) => alias.name === internalMessageType);
if (!unionTypeAlias) {
return [];
}
const sendMethods = [];
// If the underlying type is a union, extract its alternatives
if (unionTypeAlias.underlyingType.kind === 'union') {
const alternatives = unionTypeAlias.underlyingType.alternatives;
for (const alternative of alternatives) {
if (alternative.kind === 'type_identifier') {
const messageStructName = alternative.name;
// Find the corresponding struct
const messageStruct = structures.find((struct) => struct.name === messageStructName);
if (messageStruct) {
// Convert message struct name to send method name
// e.g., "increment_message" -> "sendIncrement", "IncrementMessage" -> "sendIncrement"
let methodName = messageStruct.name;
if (methodName.endsWith('Message')) {
methodName = methodName.slice(0, -7); // Remove "Message"
}
// Normalize the method name (convert snake_case to camelCase)
const normalizedName = WrapperGenerator.normalizeMethodName(methodName);
methodName = `send${WrapperGenerator.capitalize(normalizedName)}`;
const sendMethod = {
name: methodName,
messageType: messageStruct.name,
parameters: messageStruct.fields,
};
sendMethods.push(sendMethod);
}
}
}
}
return sendMethods;
}
/**
* Generate class end
*/
static generateClassEnd() {
return '}';
}
/**
* Capitalize first letter
*/
static capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* Convert snake_case to camelCase
*/
static snakeToCamelCase(str) {
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}
/**
* Convert method name to camelCase (handles both snake_case and PascalCase)
*/
static normalizeMethodName(methodName) {
// First convert snake_case to camelCase
let normalized = WrapperGenerator.snakeToCamelCase(methodName);
// If the result starts with uppercase, make it lowercase (PascalCase -> camelCase)
if (normalized.charAt(0) === normalized.charAt(0).toUpperCase()) {
normalized = normalized.charAt(0).toLowerCase() + normalized.slice(1);
}
return normalized;
}
}
exports.WrapperGenerator = WrapperGenerator;
//# sourceMappingURL=wrapper-generator.js.map