@kinobi-so/renderers-core
TypeScript icon, indicating that this package has built-in type declarations

0.21.2 • Public • Published

Kinobi ➤ Renderers ➤ Core

npm npm-downloads

This package provides the core utility for generating clients from Kinobi IDLs. Its aim is mainly to provide helpers for other renderer packages such as @kinobi-so/renderers-js and @kinobi-so/renderers-rust.

Installation

pnpm install @kinobi-so/renderers-core

[!NOTE] This package is not included in the main kinobi package.

Filesystem wrappers

This package offers several helper functions that delegate to the native Filesystem API — i.e. node:fs — when using the Node.js runtime. However, in any other environment — such as the browser — these functions will throw a KINOBI_ERROR__NODE_FILESYSTEM_FUNCTION_UNAVAILABLE error as a Filesystem API is not available. This enables us to write renderers regardless of the runtime environment.

// Reads the UTF-8 content of a file as a JSON object.
const json = readJson<MyJsonDefinition>(filePath);

// Creates a directory at the given path, recursively.
createDirectory(newDirectoryPath);

// Deletes a directory, recursively, if it exists.
deleteDirectory(directoryPath);

// Creates a new file at the given path with the given content.
// Creates its parent directory, recursively, if it does not exist.
createFile(filePath, content);

Render maps

The RenderMap class is a utility class that helps manage a collection of files to be rendered. It acts as a middleman between the logic that generates the content and the logic that writes the content to the filesystem. As such, it provides a way to access the generated content outside an environment that supports the Filesystem API — such as the browser. It also helps us write tests about the generated code without having to write it to the filesystem.

Adding content to a RenderMap

The add content to a RenderMap, you can use the add method by providing a path and the content to be written to that path.

Note that the path should be relative to the base directory that will be provided when writing the RenderMap to the filesystem.

const renderMap = new RenderMap()
    .add('programs/token.ts', 'export type TokenProgram = { /* ... */ }')
    .add('accounts/mint.ts', 'export type MintAccount = { /* ... */ }')
    .add('instructions/transfer.ts', 'export function getTransferInstruction = { /* ... */ }');

Additionally, you can use the mergeWith method to merge multiple RenderMap instances together.

const renderMapA = new RenderMap().add('programs/programA.ts', 'export type ProgramA = { /* ... */ }');
const renderMapB = new RenderMap().add('programs/programB.ts', 'export type ProgramB = { /* ... */ }');
const renderMapC = new RenderMap().mergeWith(renderMapA, renderMapB);

Removing content from a RenderMap

To remove files from a RenderMap, simply use the remove method by providing the relative path of the file to be removed.

renderMap.remove('programs/token.ts');

Accessing content from a RenderMap

The RenderMap class provides several methods to access the content of the files it manages. The get method returns the content of a file from its relative path. If the file does not exist on the RenderMap, a KINOBI_ERROR__VISITORS__RENDER_MAP_KEY_NOT_FOUND error will be thrown.

const content: string = renderMap.get('programs/token.ts');

To safely access the content of a file without throwing an error, you can use the safeGet method. This method returns the content of a file from its relative path, or undefined if the file does not exist.

const content: string | undefined = renderMap.safeGet('programs/token.ts');

The has and isEmpty methods can also be used to verify the existence of files in the RenderMap.

const hasTokenProgram = renderMap.has('programs/token.ts');
const hasNoFiles = renderMap.isEmpty();

Finally, the contains method can be used to check if a file contains a specific string or matches a regular expression.

const hasTokenProgram = renderMap.contains('programs/token.ts', 'export type TokenProgram = { /* ... */ }');
const hasMintAccount = renderMap.contains('programs/token.ts', /MintAccount/);

Tranforming content from a RenderMap

To map the content of files inside a RenderMap, you can use the mapContent method. This method accepts a function that takes the content of a file and returns a new content.

renderMap.mapContent(content => `/** Prefix for all files */\n\n${content}`);

An asynchronous version of this method called mapContentAsync is also available in case the transformation function needs to be asynchronous.

await renderMap.mapContentAsync(async content => {
    const transformedContent = await someAsyncFunction(content);
    return `/** Prefix for all files */\n\n${transformedContent}`;
});

Writing a RenderMap to the filesystem

When the RenderMap is ready to be written to the filesystem, you can use the write method by providing the base directory where all files should be written. Any relative path provided by the add method will be appended to this base directory.

const renderMap = new RenderMap()
    .add('programs/token.ts', 'export type TokenProgram = { /* ... */ }')
    .add('accounts/mint.ts', 'export type MintAccount = { /* ... */ }');

renderMap.write('src/generated');
// In this example, files will be written to:
// - src/generated/programs/token.ts
// - src/generated/accounts/mint.ts.

Using visitors

When building renderers, you will most likely create a visitor that traverses the Kinobi IDL and returns a RenderMap. That way, you can test the generated content without having to write it to the filesystem. For instance, the @kinobi-so/renderers-js package exports a getRenderMapVisitor function that does just that.

import { getRenderMapVisitor } from '@kinobi-so/renderers-js';

const renderMap = kinobi.accept(getRenderMapVisitor());

If you have access to a visitor that returns a RenderMap — also described as Visitor<RenderMap> — then, you can wrap it inside the writeRenderMapVisitor to directly write the content to the filesystem at the given base directory.

import { getRenderMapVisitor } from '@kinobi-so/renderers-js';

kinobi.accept(writeRenderMapVisitor(getRenderMapVisitor(), 'src/generated'));

Note however that, if you are writing your own renderer, you should probably offer a higher-level visitor that includes this logic and also does some additional work such as deleting the base directory before writing the new content if it already exists.

For instance, the recommended way of using the @kinobi-so/renderers-js package is to use the following renderVisitor function.

import { renderVisitor } from '@kinobi-so/renderers-js';

kinobi.accept(renderVisitor('src/generated'));

Here's a simple example of how to set up the basis of a renderer from an existing getRenderMapVisitor.

import { deleteDirectory } from '@kinobi-so/renderers-core';
import { rootNodeVisitor, visit } from '@kinobi-so/visitors-core';

type RenderOptions = {
    deleteFolderBeforeRendering?: boolean;
    // Any other options...
};

export function renderVisitor(path: string, options: RenderOptions = {}) {
    return rootNodeVisitor(async root => {
        // Delete existing generated folder.
        if (options.deleteFolderBeforeRendering ?? true) {
            deleteDirectory(path);
        }

        // Render the new files.
        visit(root, writeRenderMapVisitor(getRenderMapVisitor(options), path));
    });
}

Package Sidebar

Install

npm i @kinobi-so/renderers-core

Weekly Downloads

250

Version

0.21.2

License

MIT

Unpacked Size

62.1 kB

Total Files

21

Last publish

Collaborators

  • lorisleiva