npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@kinobi-so/renderers-core

v0.22.0

Published

Core types and helpers for Kinobi renderers to use

Downloads

1,584

Readme

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));
    });
}