@kinobi-so/renderers-core
Advanced tools
Comparing version 0.20.5 to 0.20.6
{ | ||
"name": "@kinobi-so/renderers-core", | ||
"version": "0.20.5", | ||
"version": "0.20.6", | ||
"description": "Core types and helpers for Kinobi renderers to use", | ||
@@ -38,5 +38,5 @@ "exports": { | ||
"dependencies": { | ||
"@kinobi-so/errors": "0.20.5", | ||
"@kinobi-so/nodes": "0.20.5", | ||
"@kinobi-so/visitors-core": "0.20.5" | ||
"@kinobi-so/errors": "0.20.6", | ||
"@kinobi-so/nodes": "0.20.6", | ||
"@kinobi-so/visitors-core": "0.20.6" | ||
}, | ||
@@ -43,0 +43,0 @@ "license": "MIT", |
182
README.md
# Kinobi ➤ Renderers ➤ Core | ||
TODO | ||
[![npm][npm-image]][npm-url] | ||
[![npm-downloads][npm-downloads-image]][npm-url] | ||
[npm-downloads-image]: https://img.shields.io/npm/dm/@kinobi-so/renderers-core.svg?style=flat | ||
[npm-image]: https://img.shields.io/npm/v/@kinobi-so/renderers-core.svg?style=flat&label=%40kinobi-so%2Frenderers-core | ||
[npm-url]: https://www.npmjs.com/package/@kinobi-so/renderers-core | ||
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`](../renderers-js) and [`@kinobi-so/renderers-rust`](../renderers-rust). | ||
## Installation | ||
```sh | ||
pnpm install @kinobi-so/renderers-core | ||
``` | ||
> [!NOTE] | ||
> This package is **not** included in the main [`kinobi`](../library) 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. | ||
```ts | ||
// 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. | ||
```ts | ||
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. | ||
```ts | ||
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. | ||
```ts | ||
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. | ||
```ts | ||
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. | ||
```ts | ||
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`. | ||
```ts | ||
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. | ||
```ts | ||
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. | ||
```ts | ||
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. | ||
```ts | ||
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. | ||
```ts | ||
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`](../renderers-js) package exports a `getRenderMapVisitor` function that does just that. | ||
```ts | ||
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. | ||
```ts | ||
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. | ||
```ts | ||
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`. | ||
```ts | ||
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)); | ||
}); | ||
} | ||
``` |
58706
184
+ Added@kinobi-so/errors@0.20.6(transitive)
+ Added@kinobi-so/node-types@0.20.6(transitive)
+ Added@kinobi-so/nodes@0.20.6(transitive)
- Removed@kinobi-so/errors@0.20.5(transitive)
- Removed@kinobi-so/node-types@0.20.5(transitive)
- Removed@kinobi-so/nodes@0.20.5(transitive)
Updated@kinobi-so/errors@0.20.6
Updated@kinobi-so/nodes@0.20.6