diff --git a/src/morphix/README.md b/src/morphix/README.md index 118d3497..b29480de 100644 --- a/src/morphix/README.md +++ b/src/morphix/README.md @@ -42,8 +42,9 @@ import { morphix } from "@sigyn/morphix"; await morphix("Hello {name | capitalize}", { name: "john" }); ``` -> [!NOTE] -> `morphix()` is async because it supports async **functions** +> [!IMPORTANT] +> `morphix()` is asynchrone because it supports async **functions** + ## 🌐 API ### `morphix` @@ -68,22 +69,31 @@ Data to interpolate into template. The keys should be a valid JS identifier or number (a-z, A-Z, 0-9). -**options** -Type: `object` - -**ignoreMissing** -Type: `boolean` -Default: `false` +#### Options -By default, Morphix throws a MissingValueError when a placeholder resolves to undefined. With this option set to true, it simply ignores it and leaves the placeholder as is. - -**transform** -Type: `(data: { value: unknown; key: string }) => unknown` (default: `({value}) => value)`) - -Performs arbitrary operation for each interpolation. If the returned value was undefined, it behaves differently depending on the ignoreMissing option. Otherwise, the returned value will be interpolated into a string and embedded into the template. +```ts +type MorphixFunction = (value: string) => Promise<string> | string; + +export interface MorphixOptions { + /** + * Performs arbitrary operations for each interpolation. + * If the returned value is undefined, the behavior depends on the ignoreMissing option. + * Otherwise, the returned value is converted to a string and embedded into the template. + */ + transform?: (data: { value: unknown; key: string }) => unknown; + /** + * By default, Morphix throws a MissingValueError when a placeholder resolves to undefined. + * If this option is set to true, it simply ignores the unresolved placeholder and leaves it as is. + * + * @default false + */ + ignoreMissing?: boolean; + customFunctions?: Record<string, MorphixFunction>; +} +``` -**MissingValueError** -Exposed for instance checking. +> [!NOTE] +> MissingValueError is exported, which is useful for instance and type checking. ## 📦 Functions @@ -97,20 +107,31 @@ Retrieve host of a given IP. It uses `dns.reverse`. If it fails to retrieve the host, it returns the ip instead. -## 🖋️ Interfaces +### Adding custom functions + +Morphix lets you inject your own custom functions. ```ts -interface MorphixOptions { - transform?: (data: { - value: unknown; - key: string; - }) => unknown; - ignoreMissing?: boolean; -} +import { morphix } from "@sigyn/morphix"; +import assert from "node:assert"; + +const transformedData = await morphix( + "{ data | lower }", + { data: "HELLO WORLD" }, + { + customFunctions: { + lower: (value) => value.toLowerCase() + } + } +); +assert.strictEqual( + transformedData, + "hello world" +); ``` ## Credits -This package is heavily inspired by [pupa](https://github.com/sindresorhus/pupa). Morphix is a fork with function support and doesn't support HTML escape. +This package is heavily inspired by [pupa](https://github.com/sindresorhus/pupa). Morphix is a fork that includes function support and does not support HTML escape. ## License MIT diff --git a/src/morphix/src/index.ts b/src/morphix/src/index.ts index f16a53da..174d8ad2 100644 --- a/src/morphix/src/index.ts +++ b/src/morphix/src/index.ts @@ -4,14 +4,28 @@ import { capitalize, dnsresolve } from "./functions"; // CONSTANTS -const kFunctions = { +const kDefaultFunctions: Record<string, MorphixFunction> = { capitalize, dnsresolve }; +export type MorphixFunction = (value: string) => Promise<string> | string; + export interface MorphixOptions { + /** + * Performs arbitrary operations for each interpolation. + * If the returned value is undefined, the behavior depends on the ignoreMissing option. + * Otherwise, the returned value is converted to a string and embedded into the template. + */ transform?: (data: { value: unknown; key: string }) => unknown; + /** + * By default, Morphix throws a MissingValueError when a placeholder resolves to undefined. + * If this option is set to true, it simply ignores the unresolved placeholder and leaves it as is. + * + * @default false + */ ignoreMissing?: boolean; + customFunctions?: Record<string, MorphixFunction>; } export class MissingValueError extends Error { @@ -31,8 +45,10 @@ export async function morphix( ) { const { transform = ({ value }) => value, - ignoreMissing = false + ignoreMissing = false, + customFunctions = {} } = options; + const functions = { ...customFunctions, ...kDefaultFunctions }; if (typeof template !== "string") { throw new TypeError(`Expected a \`string\` in the first argument, got \`${typeof template}\``); @@ -62,7 +78,7 @@ export async function morphix( return String(transformedValue); } - return await kFunctions[func](String(transformedValue)); + return await functions[func](String(transformedValue)); }; const braceFnRegex = /{\s{0,1}([a-z0-9-.]*)\s{0,1}(?:\|\s{0,1}((?:(?!{)[a-z0-9-]*)*?)\s{0,1})?}/gi; diff --git a/src/morphix/test/index.spec.mts b/src/morphix/test/index.spec.mts index f2f3cb65..87652816 100644 --- a/src/morphix/test/index.spec.mts +++ b/src/morphix/test/index.spec.mts @@ -6,7 +6,7 @@ import { describe, it } from "node:test"; import esmock from "esmock"; // Import Internal Dependencies -import { morphix } from "../dist/index.mjs"; +import { morphix, type MorphixFunction } from "../dist/index.mjs"; describe("Morphix", () => { it("main", async() => { @@ -100,4 +100,22 @@ describe("Morphix", () => { }); assert.equal(await morphix("host: {foo | dnsresolve}", { foo: "8.8.8.8" }), "host: 8.8.8.8"); }); + + describe("customFunctions", () => { + it("should use a new custom 'replace' function", async() => { + const customFunctions: Record<string, MorphixFunction> = { + replace: (value) => value.replaceAll("o", "i") + }; + + const computedStr = await morphix( + "{ foo | replace }", + { foo: "foo" }, + { customFunctions } + ); + assert.strictEqual( + computedStr, + "fii" + ); + }); + }); });