diff --git a/README.md b/README.md index 0c59e78..7769cd0 100644 --- a/README.md +++ b/README.md @@ -16,17 +16,26 @@ - [@synstack/xml](./packages/xml/README.md) - Lax, non spec-compliant XML utils tailored for LLMs - [@synstack/yaml](./packages/yaml/README.md) - Safe and opiniated YAML serialization and deserialization -#### Functional programming +#### System Utilities + +- [@synstack/enhance](./packages/enhance/README.md) - Utility functions for enhancing objects and functions with additional capabilities +- [@synstack/fs-cache](./packages/fs-cache/README.md) - File system caching with deep equality checks and automatic invalidation +- [@synstack/git](./packages/git/README.md) - Git operations with type-safe command building and execution +- [@synstack/glob](./packages/glob/README.md) - Type-safe glob pattern matching and file filtering utilities + +#### Functional Programming - [@synstack/pipe](./packages/pipe/README.md) - Simple typesafe pipe utility for Functional Programming - [@synstack/resolved](./packages/resolved/README.md) - A piping utility which preserves the sync/async state of the value -#### Text manipulation +#### Text and Document Processing +- [@synstack/llm](./packages/llm/README.md) - Type-safe LLM message handling with support for text, images, and tool calls +- [@synstack/markdown](./packages/markdown/README.md) - Type-safe markdown processing with YAML frontmatter support - [@synstack/str](./packages/str/README.md) - Advanced chainable string manipulation - [@synstack/text](./packages/text/README.md) - String templating as it was meant to be -#### Web scraping +#### Web Scraping - [@synstack/web](./packages/web/README.md) - Web scraping utilities diff --git a/packages/enhance/README.md b/packages/enhance/README.md index 0464edc..8be7bfe 100644 --- a/packages/enhance/README.md +++ b/packages/enhance/README.md @@ -1 +1,153 @@ -# @synstack/enhance +# @synstack/enhance + +> Type-safe object enhancement with proxy-based method extension + +This package provides a type-safe way to extend JavaScript objects with additional methods while maintaining access to the original object through proxies. + +> [!WARNING] +> This package is included in the [@synstack/synscript](https://github.com/pAIrprogio/synscript) package. It is not recommended to install both packages at the same time. + +## What is it for? + +Sometimes you need to add functionality to existing objects without modifying their prototype or risking property collisions. This package provides a safe way to enhance objects with new methods while maintaining type safety: + +```typescript +import { enhance } from "@synstack/enhance"; + +// Create an extension with new methods +const objExtensions = { + stringify: function() { return JSON.stringify(this) }, + clone: function() { return { ...this } } +}; + +// Enhance an object with new methods +const obj = { name: "example", value: 42 }; +const enhanced = enhance("object", obj, objExtensions); + +// Use the enhanced object +console.log(enhanced.stringify()); // '{"name":"example","value":42}' +console.log(enhanced.clone()); // { name: "example", value: 42 } + +// Access the original object +console.log(enhanced.$()); // { name: "example", value: 42 } +``` + +## Installation + +```bash +# Using npm +npm install @synstack/enhance + +# Using yarn +yarn add @synstack/enhance + +# Using pnpm +pnpm add @synstack/enhance +``` + +## Features + +### Object Enhancement + +The package provides two main functions for enhancing objects: + +#### enhance() + +The `enhance()` function combines an object with extension methods: + +```typescript +import { enhance } from "@synstack/enhance"; + +// Define extension methods +const loggerExtensions = { + log: function() { console.log(JSON.stringify(this)) }, + getTimestamp: function() { return { ...this, timestamp: Date.now() } } +}; + +// Enhance an object +const data = { id: 1, message: "Hello" }; +const enhanced = enhance("logger", data, loggerExtensions); + +// Use enhanced methods +enhanced.log(); // Logs: {"id":1,"message":"Hello"} +const timestamped = enhanced.getTimestamp(); // { id: 1, message: "Hello", timestamp: 1234567890 } +``` + +#### enhanceFactory() + +Create reusable enhancers with `enhanceFactory()`: + +```typescript +import { enhanceFactory } from "@synstack/enhance"; + +// Create a reusable enhancer +const withLogging = enhanceFactory("logger", { + log: function() { console.log(JSON.stringify(this)) }, + getTimestamp: function() { return { ...this, timestamp: Date.now() } } +}); + +// Enhance multiple objects +const obj1 = withLogging({ id: 1, name: "First" }); +const obj2 = withLogging({ id: 2, name: "Second" }); + +obj1.log(); // Logs: {"id":1,"name":"First"} +obj2.log(); // Logs: {"id":2,"name":"Second"} +``` + +## API Reference + +### enhance() + +```typescript +function enhance( + name: TName, + obj: TBaseObject, + extendObj: TExtension +): Enhanced +``` + +- `name`: Unique identifier for this enhancement +- `obj`: The base object to enhance +- `extendObj`: Object containing extension methods +- Returns: A proxy that combines the base object with extension methods + +### enhanceFactory() + +```typescript +function enhanceFactory( + name: TName, + extendObj: TExtension +): (obj: TBaseObject) => Enhanced +``` + +- `name`: Unique identifier for this enhancement factory +- `extendObj`: Object containing extension methods +- Returns: A function that enhances objects with the provided extensions + +### Enhanced Type + +```typescript +type Enhanced = { + $: TBaseObject; + [ENHANCER_NAME]: TName; +} & TExtension & TBaseObject +``` + +A type representing an enhanced object that combines: +- Original object properties (`TBaseObject`) +- Extension methods (`TExtension`) +- Special `$` property to access the original object +- Symbol property to identify the enhancer + +## TypeScript Support + +This package is written in TypeScript and provides full type definitions: + +- Generic type parameters for base objects and extensions +- Type-safe access to both original and enhanced methods +- Proper typing for extension method context (`this`) +- IntelliSense support for all enhanced properties + +## License + +Apache-2.0 - see LICENSE file for details. diff --git a/packages/enhance/package.json b/packages/enhance/package.json index e26f573..69bb92d 100644 --- a/packages/enhance/package.json +++ b/packages/enhance/package.json @@ -5,11 +5,15 @@ "access": "public" }, "version": "1.1.2", - "description": "Safely enhance a JS object with additional properties", + "description": "Type-safe object enhancement with proxy-based method extension - for objects only, not primitives", "keywords": [ "proxy", - "object", - "enhance" + "object-enhancement", + "type-safe", + "method-extension", + "object-only", + "typescript", + "javascript-proxy" ], "author": { "name": "pAIrprog", diff --git a/packages/enhance/src/enhance.lib.ts b/packages/enhance/src/enhance.lib.ts index 74d76ed..3b59d7d 100644 --- a/packages/enhance/src/enhance.lib.ts +++ b/packages/enhance/src/enhance.lib.ts @@ -1,12 +1,31 @@ +/** + * Symbol used to identify enhanced objects and their original enhancer name + */ export const ENHANCER_NAME = Symbol("EnhancerName"); // The order of the types is important as it affects overriding +/** + * Type representing an enhanced object that combines base object properties with extensions + * @template TName - The name identifier for the enhancer + * @template TBaseObject - The type of the original object being enhanced + * @template TExtension - The type of the extension object adding new functionality + */ export type Enhanced< TName extends string, TBaseObject extends object, TExtension extends object, > = { $: TBaseObject; [ENHANCER_NAME]: TName } & TExtension & TBaseObject; +/** + * Enhances an object by combining it with extension methods while maintaining access to the original object + * @template TName - The name identifier for the enhancer + * @template TBaseObject - The type of the object to enhance + * @template TExtension - The type of the extension object containing new methods + * @param name - Unique name for this enhancement + * @param obj - The base object to enhance + * @param extendObj - Object containing extension methods + * @returns A proxy that combines the base object with extension methods + */ export const enhance = < TName extends string, TBaseObject extends object | (object & { [ENHANCER_NAME]: TName; $: object }), @@ -36,6 +55,14 @@ export const enhance = < }) as unknown as Enhanced; }; +/** + * Creates a reusable enhancer function that applies the same extensions to multiple objects + * @template TName - The name identifier for the enhancer + * @template TExtension - The type of the extension object containing new methods + * @param name - Unique name for this enhancement factory + * @param extendObj - Object containing extension methods + * @returns A function that can enhance objects with the provided extensions + */ export const enhanceFactory = ( name: TName, diff --git a/packages/fs-cache/README.md b/packages/fs-cache/README.md index 47ab659..0835ee7 100644 --- a/packages/fs-cache/README.md +++ b/packages/fs-cache/README.md @@ -1 +1,143 @@ -# @synstack/fs-cache +# @synstack/fs-cache + +> File system-based caching with type-safe function memoization + +This package provides a strongly-typed caching system that stores function results in the file system, with support for custom cache keys and value locking. + +> [!WARNING] +> This package is included in the [@synstack/synscript](https://github.com/pAIrprogio/synscript) package. It is not recommended to install both packages at the same time. + +## What is it for? + +Sometimes you need to cache expensive function results between program runs. This package makes it easy to cache function outputs to disk with type safety: + +```typescript +import { fsCache } from "@synstack/fs-cache"; + +// Create a cache in the .cache directory +const cache = fsCache(".cache") + .key(["expensive", "function"]) + .pretty(true); + +// Cache an expensive function +const expensiveFunction = cache.fn(async (input: string) => { + // Simulate expensive operation + await new Promise(resolve => setTimeout(resolve, 1000)); + return `Processed: ${input}`; +}); + +// First call: takes 1 second +await expensiveFunction("test"); // "Processed: test" + +// Second call: instant (reads from cache) +await expensiveFunction("test"); // "Processed: test" +``` + +## Installation + +```bash +# Using npm +npm install @synstack/fs-cache + +# Using yarn +yarn add @synstack/fs-cache + +# Using pnpm +pnpm add @synstack/fs-cache +``` + +## Features + +### Function Caching + +Cache expensive function results with type safety: + +```typescript +import { fsCache } from "@synstack/fs-cache"; + +const cache = fsCache(".cache"); + +// Cache with static key +const cachedFn = cache + .key("myFunction") + .fn((x: number) => x * x); + +// Cache with dynamic key based on arguments +const cachedFn2 = cache + .key([ + "myFunction", + (arg: string) => arg.length.toString() + ]) + .fn((arg: string) => arg.toUpperCase()); +``` + +### Cache Control + +Fine-grained control over cache behavior: + +```typescript +// Pretty-print cached JSON +const cache = fsCache(".cache").pretty(true); + +// Custom cache key generation +const cache2 = fsCache(".cache") + .signatureFn((arg: string) => arg.toLowerCase()) + .key("normalized"); + +// Lock cached values +await cache.lock(true, ["key"]); // Prevent updates +await cache.lock(false, ["key"]); // Allow updates + +// Manual cache operations +const [status, value] = await cache.get(["key"]); +await cache.set(["key"], "value"); +await cache.setDefault(["key"], "default"); +``` + +## API Reference + +### FsCache + +The main class for file system caching: + +#### Creation +- `fsCache(cwd)` - Create cache in working directory +- `key(keys)` - Set cache key or key generators +- `signatureFn(fn)` - Set input signature function +- `pretty(enabled)` - Enable/disable pretty JSON + +#### Cache Operations +- `get(args)` - Get cached value +- `set(args, value)` - Set cache value +- `setDefault(args, value)` - Set default value +- `lock(isLocked, args)` - Lock/unlock cached value +- `fn(function)` - Create cached function wrapper + +### Types + +#### KeyFn +```typescript +type KeyFn = + | string + | ((...args: TFnArgs) => string); +``` + +#### SignatureFn +```typescript +type SignatureFn = ( + ...args: TFnArgs +) => any; +``` + +## TypeScript Support + +This package is written in TypeScript and provides full type definitions: + +- Generic type parameters for function arguments +- Type-safe function wrapping +- Strongly typed cache operations +- IntelliSense support for all methods + +## License + +Apache-2.0 - see LICENSE file for details. diff --git a/packages/fs-cache/package.json b/packages/fs-cache/package.json index 8b12c1b..d93fac4 100644 --- a/packages/fs-cache/package.json +++ b/packages/fs-cache/package.json @@ -5,10 +5,16 @@ "access": "public" }, "version": "1.3.0", - "description": "Human-friendly file system caching", + "description": "Type-safe file system caching with function memoization and persistent storage", "keywords": [ "cache", - "memoize" + "memoize", + "file-system", + "typescript", + "type-safe", + "persistent", + "function-cache", + "fs" ], "author": { "name": "pAIrprog", diff --git a/packages/fs-cache/src/deepEqual.lib.ts b/packages/fs-cache/src/deepEqual.lib.ts index 9d5efcb..a83f386 100644 --- a/packages/fs-cache/src/deepEqual.lib.ts +++ b/packages/fs-cache/src/deepEqual.lib.ts @@ -1,3 +1,15 @@ +/** + * Performs a deep equality comparison between two values + * @param obj1 - First value to compare + * @param obj2 - Second value to compare + * @returns True if the values are deeply equal, false otherwise + * + * This function handles: + * - Primitive values (strings, numbers, booleans) + * - Objects and arrays (recursive comparison) + * - null and undefined values + * - Properties with undefined values are ignored + */ export function deepEqual(obj1: any, obj2: any) { if (obj1 === obj2) { return true; diff --git a/packages/fs/README.md b/packages/fs/README.md index 6efee1e..e69fe95 100644 --- a/packages/fs/README.md +++ b/packages/fs/README.md @@ -9,30 +9,33 @@ This package provides a strongly-typed, chainable API for file system operations ## What is it for? -Working with files and directories should be simple and type-safe. This package turns verbose file operations into chainable, strongly-typed commands: +Working with files and directories should be simple, type-safe, and predictable. This package provides an immutable API where each operation returns a new instance, making it safe to chain operations while preserving original file references: ```typescript import { file, dir } from "@synstack/fs"; -// Read and validate JSON with schema -const configFile = file("./config.json") - .schema(ConfigSchema) - .read.json(); +// Each operation returns a new instance, preserving the original +const configFile = file("./config.json"); +const validatedConfig = configFile.schema(ConfigSchema); // New instance with schema +const configData = await validatedConfig.read.json(); // Read validated JSON -// Write text files with different encodings -const templateFile = file("./template.txt") - .write.text("Hello ${name}!"); +// Chain operations while maintaining immutability +const templateFile = file("./template.txt"); +const updatedTemplate = templateFile + .write.text("Hello ${name}!"); // Returns new instance -// Find and filter files by glob patterns and MIME types -const images = dir("./assets") - .glob("**/*.{png,jpg}") - .filterMimeTypes("image/png", "image/jpeg"); +// Directory operations are also immutable +const rootDir = dir("./assets"); +const imagesDir = rootDir.to("images"); // New instance for subdirectory +const imageFiles = await imagesDir + .glob("**/*.{png,jpg}") // Returns new array + .filterMimeTypes("image/png", "image/jpeg"); // Returns filtered array -// Work with relative paths safely +// Each path operation returns a new instance const sourceFile = file("/path/to/source.ts"); const targetFile = sourceFile - .toFile("../dist/output.js") - .write.text(compiledContent); + .toFile("../dist/output.js"); // New instance with different path +await targetFile.write.text(compiledContent); // Handle JSON with pretty formatting const packageJson = file("package.json") @@ -146,7 +149,8 @@ The `FsFile` class provides methods for working with individual files: #### Creation - `file(...paths)` - Create a new file instance from path segments -- `schema(zodSchema)` - Add schema validation for JSON/YAML operations +- `schema(zodSchema)` - Create new instance with schema validation +- All operations return new instances, preserving original file references #### Read Operations - `read.text()` - Read file as text @@ -190,6 +194,7 @@ The `FsDir` class provides methods for working with directories: - `dir(...paths)` - Create new directory instance - `to(relativePath)` - Create new subdirectory instance - `file(relativePath)` - Create new file instance in directory +- All operations return new instances, preserving original directory references #### Directory Operations - `exists()` - Check if directory exists diff --git a/packages/git/README.md b/packages/git/README.md index a194d54..c0c978f 100644 --- a/packages/git/README.md +++ b/packages/git/README.md @@ -1 +1,90 @@ -# @synstack/pipe +# @synstack/git + +> Git operations with TypeScript support + +This package provides a strongly-typed interface for common Git operations, making it easy to interact with Git repositories programmatically. + +> [!WARNING] +> This package is included in the [@synstack/synscript](https://github.com/pAIrprogio/synscript) package. It is not recommended to install both packages at the same time. + +## What is it for? + +When you need to interact with Git repositories programmatically, this package provides a simple and type-safe way to perform common Git operations: + +```typescript +import { ls } from "@synstack/git"; + +// List all files in the repository (tracked, modified, and untracked) +const files = await ls("./my-repo"); +console.log(files); +// [ +// "src/index.ts", +// "package.json", +// "README.md", +// "new-file.txt" // untracked +// ] +``` + +## Installation + +```bash +# Using npm +npm install @synstack/git + +# Using yarn +yarn add @synstack/git + +# Using pnpm +pnpm add @synstack/git +``` + +## Features + +### File Listing + +List all files in a Git repository, including: +- Tracked files +- Modified files +- Untracked files (respecting .gitignore) + +```typescript +import { ls } from "@synstack/git"; + +// List files in current directory +const files = await ls(); + +// List files in specific directory +const repoFiles = await ls("./my-repo"); + +// List files in specific subdirectory +const srcFiles = await ls("./my-repo", "src"); +``` + +## API Reference + +### Functions + +#### `ls(cwd?: string, relativePath?: string): Promise` + +Lists all Git-tracked, modified, and untracked files. + +- **Parameters:** + - `cwd` (optional): Working directory for Git commands (defaults to ".") + - `relativePath` (optional): Path relative to working directory to list files from (defaults to ".") +- **Returns:** Promise resolving to array of file paths +- **Example:** + ```typescript + const files = await ls("./repo", "src"); + // ["src/index.ts", "src/utils.ts"] + ``` + +## TypeScript Support + +This package is written in TypeScript and provides full type definitions: +- Type-safe function parameters +- IntelliSense support for all functions +- Strongly typed return values + +## License + +Apache-2.0 - see LICENSE file for details. diff --git a/packages/git/package.json b/packages/git/package.json index d2263e7..22ee1bc 100644 --- a/packages/git/package.json +++ b/packages/git/package.json @@ -5,6 +5,16 @@ "access": "public" }, "version": "1.1.2", + "description": "Type-safe Git operations interface for programmatic repository management", + "keywords": [ + "git", + "typescript", + "type-safe", + "repository", + "file-listing", + "git-files", + "version-control" + ], "author": { "name": "pAIrprog", "url": "https://pairprog.io" diff --git a/packages/git/src/git.lib.ts b/packages/git/src/git.lib.ts index 7f8777d..ada84dd 100644 --- a/packages/git/src/git.lib.ts +++ b/packages/git/src/git.lib.ts @@ -1,5 +1,18 @@ import { execaCommand } from "execa"; +/** + * Lists all git-tracked, modified, and untracked files in a directory + * @param cwd - Working directory for git commands (defaults to ".") + * @param relativePath - Path relative to working directory to list files from (defaults to ".") + * @returns Promise resolving to array of file paths + * + * This function combines the output of multiple git commands to list: + * - Tracked files (git ls-files) + * - Modified files (git ls-files -m) + * - Untracked files (git ls-files --others --exclude-standard) + * + * Empty paths are filtered out from the results. + */ export async function ls(cwd: string = ".", relativePath: string = ".") { const res = await execaCommand( `( git ls-files ${relativePath}; git ls-files -m ${relativePath} ; git ls-files --others --exclude-standard ${relativePath} ) | sort | uniq`, diff --git a/packages/glob/README.md b/packages/glob/README.md index 6029d93..eeea4ca 100644 --- a/packages/glob/README.md +++ b/packages/glob/README.md @@ -1 +1,146 @@ -# @synstack/markdown +# @synstack/glob + +> Type-safe glob pattern matching and file filtering utilities + +This package provides a strongly-typed interface for working with glob patterns, including file matching, filtering, and pattern capturing capabilities. + +> [!WARNING] +> This package is included in the [@synstack/synscript](https://github.com/pAIrprogio/synscript) package. It is not recommended to install both packages at the same time. + +## What is it for? + +When you need to find files using glob patterns or filter paths based on patterns, this package provides type-safe utilities: + +```typescript +import { glob, matches } from "@synstack/glob"; + +// Find all TypeScript files +const files = await glob.cwd("./src").find("**/*.ts"); +console.log(files); +// ["src/index.ts", "src/utils.ts"] + +// Check if a file matches a pattern +const isMatch = matches("src/file.ts", "**/*.ts"); +console.log(isMatch); // true + +// Exclude test files +const nonTestFiles = await glob.cwd().find(["**/*.ts", "!**/*.test.ts"]); +``` + +## Installation + +```bash +# Using npm +npm install @synstack/glob + +# Using yarn +yarn add @synstack/glob + +# Using pnpm +pnpm add @synstack/glob +``` + +## Features + +### File Finding + +Find files using glob patterns with support for exclusions: + +```typescript +import { glob } from "@synstack/glob"; + +// Find all files in a directory +const allFiles = await glob.cwd("./src").find("**/*"); + +// Find with multiple patterns +const tsFiles = await glob.cwd().find([ + "**/*.ts", // Include TypeScript files + "!**/*.test.ts", // Exclude test files + "!**/node_modules/**" // Exclude node_modules +]); + +// Synchronous finding +const configFiles = glob.cwd().findSync("**/*.config.ts"); +``` + +### Pattern Matching + +Check if paths match glob patterns: + +```typescript +import { matches, filterFactory } from "@synstack/glob"; + +// Simple matching +matches("src/file.ts", "**/*.ts"); // true +matches("test/file.ts", "!test/**"); // false + +// Create reusable filters +const filter = filterFactory([ + "**/*.ts", // Include TypeScript + "!**/*.test.ts" // Exclude tests +]); + +filter("src/utils.ts"); // true +filter("src/test.test.ts"); // false +``` + +### Pattern Capturing + +Extract values from glob patterns: + +```typescript +import { capture } from "@synstack/glob"; + +// Capture values from paths +const values = capture("**/(*)/(*).ts", "src/utils/format.ts"); +console.log(values); // ["utils", "format"] +``` + +## API Reference + +### Glob Class + +Static factory and instance methods for file finding: + +#### Creation +- `glob.cwd(dir?)` - Create instance with working directory +- `new Glob(cwd)` - Constructor (prefer using `cwd()`) + +#### Methods +- `find(patterns)` - Find files asynchronously +- `findSync(patterns)` - Find files synchronously + +### Functions + +#### `matches(path, patterns)` +Check if a path matches glob patterns: +- `path` - File path to check +- `patterns` - Glob pattern(s), prefix with "!" to exclude + +#### `capture(pattern, path)` +Extract values from a glob pattern: +- `pattern` - Glob pattern with (*) captures +- `path` - Path to extract from +- Returns captured values or null + +#### `filterFactory(patterns)` +Create a path filter function: +- `patterns` - Array of glob patterns +- Returns function that tests paths + +#### `sort(patterns)` +Split patterns into includes and excludes: +- `patterns` - Array of glob patterns +- Returns `{ includes, excludes }` + +## TypeScript Support + +This package is written in TypeScript and provides full type definitions: +- Type-safe function parameters +- Generic type parameters +- IntelliSense support +- Strongly typed return values + +## License + +Apache-2.0 - see LICENSE file for details. diff --git a/packages/glob/package.json b/packages/glob/package.json index 268d957..ae5cc68 100644 --- a/packages/glob/package.json +++ b/packages/glob/package.json @@ -5,6 +5,18 @@ "access": "public" }, "version": "1.1.3", + "description": "Type-safe glob pattern matching and file filtering utilities for Node.js", + "keywords": [ + "glob", + "pattern-matching", + "file-filtering", + "typescript", + "type-safe", + "filesystem", + "minimatch", + "file-search", + "path-matching" + ], "author": { "name": "pAIrprog", "url": "https://pairprog.io" diff --git a/packages/glob/src/glob.lib.ts b/packages/glob/src/glob.lib.ts index 1c18820..5bedfed 100644 --- a/packages/glob/src/glob.lib.ts +++ b/packages/glob/src/glob.lib.ts @@ -32,9 +32,18 @@ function flatten(array: Array | [Array]): Array { } /** - * @param filePath - * @param globs list of globs to match against globs prefixed with ! are excluded - * @returns boolean + * Checks if a file path matches any of the provided glob patterns + * @param filePath - Path to check against glob patterns + * @param globs - List of glob patterns to match against. Patterns prefixed with "!" are treated as exclusions + * @returns true if the path matches any include pattern and doesn't match any exclude pattern + * + * @example + * // Match TypeScript files + * matches("src/file.ts", "src/*.ts"); + * // Exclude specific directory + * matches("src/file.ts", "!test/*"); + * // Combine include and exclude patterns + * matches("src/file.ts", ["src/*.ts", "!test/*"]); */ export function matches( filePath: string, @@ -134,6 +143,10 @@ export class Glob { } } +/** + * Error thrown when an invalid glob pattern is provided + * @throws When a glob pattern cannot be converted to a regular expression + */ export class InvalidGlobException extends Error { constructor(glob: string) { super(`Invalid glob: ${glob}`); diff --git a/packages/json/README.md b/packages/json/README.md index 3b0c527..20ba14d 100644 --- a/packages/json/README.md +++ b/packages/json/README.md @@ -1 +1,161 @@ -# @synstack/json +# @synstack/json + +> Type-safe JSON serialization and deserialization with schema validation + +This package provides a strongly-typed interface for working with JSON data, including serialization, deserialization, and schema validation using Zod. + +> [!WARNING] +> This package is included in the [@synstack/synscript](https://github.com/pAIrprogio/synscript) package. It is not recommended to install both packages at the same time. + +## What is it for? + +When you need to work with JSON data in a type-safe way, this package provides simple, strongly-typed functions: + +```typescript +import { serialize, deserialize } from "@synstack/json"; +import { z } from "zod"; + +// Define a schema for type safety +const UserSchema = z.object({ + name: z.string(), + age: z.number(), +}); + +// Serialize data with schema validation +const jsonString = serialize( + { name: "John", age: 30 }, + { schema: UserSchema, pretty: true } +); + +// Deserialize with type inference +const user = deserialize(jsonString, { schema: UserSchema }); +console.log(user.name); // Type-safe access + +// Handle invalid JSON gracefully +try { + deserialize("invalid json"); +} catch (error) { + if (error instanceof JsonParseException) { + console.error("Failed to parse JSON:", error.message); + } +} +``` + +## Installation + +```bash +# Using npm +npm install @synstack/json + +# Using yarn +yarn add @synstack/json + +# Using pnpm +pnpm add @synstack/json +``` + +## Features + +### JSON Serialization + +Convert JavaScript objects to JSON strings with optional pretty printing: + +```typescript +import { serialize } from "@synstack/json"; + +// Basic serialization +const json = serialize({ hello: "world" }); + +// Pretty printing +const prettyJson = serialize({ hello: "world" }, { pretty: true }); +console.log(prettyJson); +// { +// "hello": "world" +// } +``` + +### Type-Safe Deserialization + +Parse JSON strings with TypeScript type inference: + +```typescript +import { deserialize } from "@synstack/json"; + +// Basic deserialization +const data = deserialize<{ count: number }>( + '{"count": 42}' +); +console.log(data.count); // TypeScript knows this is a number + +// Handle parsing errors +try { + deserialize('{"invalid": json}'); +} catch (error) { + console.error("Invalid JSON:", error.message); +} +``` + +### Schema Validation + +Use Zod schemas for runtime type checking: + +```typescript +import { serialize, deserialize } from "@synstack/json"; +import { z } from "zod"; + +// Define a schema +const ConfigSchema = z.object({ + port: z.number(), + host: z.string(), + debug: z.boolean(), +}); + +// Validate during serialization +const jsonString = serialize( + { port: 3000, host: "localhost", debug: true }, + { schema: ConfigSchema } +); + +// Validate during deserialization +const config = deserialize(jsonString, { + schema: ConfigSchema, +}); +// config has type { port: number; host: string; debug: boolean } +``` + +## API Reference + +### Functions + +#### `serialize(data, config?)` +Convert data to a JSON string: +- `data` - The data to serialize +- `config.pretty` - Whether to pretty print (indent with 2 spaces) +- `config.schema` - Optional Zod schema for validation +- Returns: JSON string + +#### `deserialize(content, config?)` +Parse a JSON string to data: +- `content` - The JSON string to parse +- `config.schema` - Optional Zod schema for validation +- Returns: Parsed data of type T + +### Classes + +#### `JsonParseException` +Error thrown when JSON parsing fails: +- Contains detailed error message +- Includes original JSON string +- Preserves underlying cause + +## TypeScript Support + +This package is written in TypeScript and provides full type definitions: +- Generic type parameters for deserialization +- Zod schema integration for runtime validation +- Type inference from schemas +- Strongly typed error handling + +## License + +Apache-2.0 - see LICENSE file for details. diff --git a/packages/llm/README.md b/packages/llm/README.md index 3b0c527..66718d1 100644 --- a/packages/llm/README.md +++ b/packages/llm/README.md @@ -1 +1,184 @@ -# @synstack/json +# @synstack/llm + +> Type-safe LLM message handling and content management + +This package provides a strongly-typed API for working with LLM messages, including support for text, images, tool calls, and tool responses. + +> [!WARNING] +> This package is included in the [@synstack/synscript](https://github.com/pAIrprogio/synscript) package. It is not recommended to install both packages at the same time. + +## What is it for? + +Working with LLM messages should be type-safe and intuitive. This package provides a structured way to create and handle different types of message content: + +```typescript +import { userMsg, assistantMsg } from "@synstack/llm"; + +// Create strongly-typed user messages with text and images +const message = userMsg`Here's my question: ${TextContent.from("How does this work?")}`; + +// Handle tool calls and responses in assistant messages +const toolCall = ToolCallContent.from({ + toolCallId: "123", + toolName: "calculator", + toolArgs: { x: 1, y: 2 } +}); +const response = assistantMsg`Let me calculate that for you ${toolCall}`; +``` + +## Installation + +```bash +# Using npm +npm install @synstack/llm + +# Using yarn +yarn add @synstack/llm + +# Using pnpm +pnpm add @synstack/llm +``` + +## Features + +### Message Building + +Create type-safe messages using template literals: + +```typescript +import { userMsg, assistantMsg } from "@synstack/llm"; + +// User messages with text and images +const userMessage = userMsg`Here's an image: ${imageContent}`; + +// Assistant messages with tool calls +const assistantMessage = assistantMsg`Let me help you with that ${toolCall}`; +``` + +### Content Types + +Work with different types of message content: + +```typescript +import { type Llm } from "@synstack/llm"; + +// Text content +const text: Llm.Message.Content.Text = { + type: "text", + text: "Hello, world!" +}; + +// Image content +const image: Llm.Message.Content.Image = { + type: "image", + image: { + type: "base64", + data: "...", + mimeType: "image/png" + } +}; + +// Tool calls are strongly typed +const toolCall: Llm.Message.Content.ToolCall = { + type: "tool_call", + toolCallId: "123", + toolName: "calculator", + toolArgs: { x: 1, y: 2 } +}; +``` + +### Tool Configuration + +Configure tool usage with type-safe definitions: + +```typescript +import { type Llm } from "@synstack/llm"; +import { z } from "zod"; + +// Define a tool with Zod schema +const calculator: Llm.Tool = { + name: "calculator", + schema: z.object({ + x: z.number(), + y: z.number() + }) +}; + +// Use in completion configuration +const config: Llm.Completion = { + temperature: 0.7, + maxTokens: 1000, + messages: [], + toolsConfig: { + type: "single", + tool: calculator + } +}; +``` + +## API Reference + +### Message Functions + +#### userMsg +- Template literal function for creating user messages +- Supports text, images, and tool responses +- Returns `Llm.User.Message` + +#### assistantMsg +- Template literal function for creating assistant messages +- Supports text and tool calls +- Returns `Llm.Assistant.Message` + +### Message Types + +#### Llm.Message.Content.Text +```typescript +{ + type: "text"; + text: string; +} +``` + +#### Llm.Message.Content.Image +```typescript +{ + type: "image"; + image: { + type: "base64"; + data: string; + mimeType: string; + }; +} +``` + +#### Llm.Message.Content.ToolCall +```typescript +{ + type: "tool_call"; + toolCallId: string; + toolName: string; + toolArgs: Record; +} +``` + +#### Llm.Message.Content.ToolResponse +```typescript +{ + type: "tool_response"; + toolCallId: string; + toolOutput: unknown; +} +``` + +## TypeScript Support + +This package is written in TypeScript and provides: +- Full type inference for messages and tools +- Type-safe parameter validation +- Strongly typed content interfaces +- Zod schema validation for tool arguments + +## License + +Apache-2.0 - see LICENSE file for details. diff --git a/packages/llm/package.json b/packages/llm/package.json index e171b38..c5008f4 100644 --- a/packages/llm/package.json +++ b/packages/llm/package.json @@ -5,14 +5,19 @@ "access": "public" }, "version": "1.4.4", - "description": "Immutable & chainable LLM tools", + "description": "Immutable & chainable LLM tools with type-safe message handling and support for text, images, tool calls, and responses", "keywords": [ "llm", "prompt", "ai", "immutable", + "chainable", "anthropic", - "openai" + "openai", + "type-safe", + "typescript", + "message", + "tool-calls" ], "author": { "name": "pAIrprog", diff --git a/packages/llm/src/contents.lib.ts b/packages/llm/src/contents.lib.ts index f7ce79c..2b3a962 100644 --- a/packages/llm/src/contents.lib.ts +++ b/packages/llm/src/contents.lib.ts @@ -3,6 +3,15 @@ import { type Llm } from "./llm.types.ts"; // Todo: decide to delete or not +/** + * Represents text content in an LLM message + * Provides a type-safe wrapper for text content with value conversion methods + * @example + * ```typescript + * const text = TextContent.from("Hello, world!"); + * const value = text.valueOf(); // Convert to Llm.Message.Content.Text + * ``` + */ export class TextContent { constructor(public readonly _text: string) {} @@ -30,6 +39,15 @@ export class TextContent { } } +/** + * Represents image content in an LLM message + * Provides a type-safe wrapper for base64-encoded images with value conversion methods + * @example + * ```typescript + * const image = ImageContent.from({ type: "base64", data: "...", mimeType: "image/png" }); + * const value = image.valueOf(); // Convert to Llm.Message.Content.Image + * ``` + */ export class ImageContent { constructor(public readonly _image: Llm.Message.Content.Image.Base64) {} @@ -53,6 +71,15 @@ export class ImageContent { } } +/** + * Represents a tool call in an LLM message + * Provides a type-safe wrapper for tool calls with value conversion methods + * @example + * ```typescript + * const toolCall = ToolCallContent.from({ toolCallId: "123", toolName: "calculator", toolArgs: { x: 1, y: 2 } }); + * const value = toolCall.valueOf(); // Convert to Llm.Message.Content.ToolCall + * ``` + */ export class ToolCallContent { constructor(public readonly _toolCall: Llm.Message.Content.ToolCall) {} @@ -73,6 +100,15 @@ export class ToolCallContent { } } +/** + * Represents a tool response in an LLM message + * Provides a type-safe wrapper for tool responses with value conversion methods + * @example + * ```typescript + * const response = ToolResponseContent.from({ toolCallId: "123", toolOutput: { result: 3 } }); + * const value = response.valueOf(); // Convert to Llm.Message.Content.ToolResponse + * ``` + */ export class ToolResponseContent { constructor( public readonly _toolResponse: Omit< @@ -104,13 +140,24 @@ export class ToolResponseContent { } } +/** + * Union type of all possible message content types + * Used for type-safe message content handling + */ type MessageContent = | TextContent | ImageContent | ToolCallContent | ToolResponseContent; +/** + * Interface defining additional methods available on message content arrays + */ interface ContentsMethods { + /** + * Filters the array to return only tool call contents + * @returns Array of ToolCallContent objects + */ toolCalls(this: Array): Array; } @@ -120,12 +167,26 @@ const customMethods: ContentsMethods = { }, }; +/** + * Enhanced type for arrays of message contents + * Adds utility methods for working with message content arrays + */ type MessageContents = Enhanced< "contents", Array, ContentsMethods >; +/** + * Creates an enhanced array of message contents with additional utility methods + * @param contents - Array of message content objects (text, images, tool calls, tool responses) + * @returns Enhanced array with additional methods like toolCalls() for filtering tool call contents + * @example + * ```typescript + * const contents = MessageContents([textContent, toolCallContent]); + * const toolCalls = contents.toolCalls(); // Get all tool calls + * ``` + */ export const MessageContents = ( contents: Array, ): MessageContents => diff --git a/packages/llm/src/llm.types.ts b/packages/llm/src/llm.types.ts index 4ea4677..0a8febe 100644 --- a/packages/llm/src/llm.types.ts +++ b/packages/llm/src/llm.types.ts @@ -3,7 +3,16 @@ import { type OneToN } from "../../shared/src/ts.utils.ts"; type $Partial = Partial; +/** + * Core namespace for LLM (Large Language Model) interactions + * Contains types for messages, completions, and tools + */ export declare namespace Llm { + /** + * Represents a tool that can be used by the LLM + * @template TName - The name of the tool + * @template TSchema - The Zod schema defining the tool's input parameters + */ export type Tool< TName extends string = string, TSchema extends ZodSchema = AnyZodObject, @@ -12,6 +21,19 @@ export declare namespace Llm { schema: TSchema; // Todo: force object schema }; + /** + * Configuration for an LLM completion request + * @property temperature - Controls randomness in the model's output (0-1) + * @property maxTokens - Maximum number of tokens to generate + * @property messages - Array of messages in the conversation + * @property system - Optional system message to set context + * @property topK - Optional parameter for top-k sampling + * @property topP - Optional parameter for nucleus sampling + * @property stopSequences - Optional array of sequences that will stop generation + * @property toolsConfig - Optional configuration for tool usage + * @property usage - Optional token usage statistics + * @property stopReason - Optional reason why generation stopped + */ export type Completion = { temperature: number; maxTokens: number; @@ -72,11 +94,23 @@ export declare namespace Llm { export type Partial = $Partial; } + /** + * Represents a message in the LLM conversation + * Can be either a user message or an assistant message + */ export type Message = User.Message | Assistant.Message; export namespace Message { + /** + * Role of the message sender + * Can be either "user" or "assistant" + */ export type Role = User.Role | Assistant.Role; + /** + * Content that can be included in a message + * Includes text, images, tool calls, and tool responses + */ export type Content = | Content.Text | Content.Image @@ -148,25 +182,49 @@ export declare namespace Llm { } } + /** + * Namespace for user-specific message types and content + */ export namespace User { export type Role = "user"; + /** + * Content types that can be included in a user message + * Supports text, images, and tool responses + */ export type Content = | Message.Content.Text | Message.Content.Image | Message.Content.ToolResponse; + /** + * Represents a message from the user + * @property role - Always "user" + * @property content - Array of content elements (text, images, tool responses) + */ export type Message = { role: Role; content: Array; }; } + /** + * Namespace for assistant-specific message types and content + */ export namespace Assistant { export type Role = "assistant"; + /** + * Content types that can be included in an assistant message + * Supports text and tool calls + */ export type Content = Message.Content.Text | Message.Content.ToolCall; + /** + * Represents a message from the assistant + * @property role - Always "assistant" + * @property content - Array of content elements (text, tool calls) + */ export type Message = { role: Role; content: Array; diff --git a/packages/llm/src/message.builder.ts b/packages/llm/src/message.builder.ts index 7cc31c4..a311417 100644 --- a/packages/llm/src/message.builder.ts +++ b/packages/llm/src/message.builder.ts @@ -6,6 +6,17 @@ import { type Base64Data } from "../../fs/src/file.lib.ts"; import { t, type Text, tParse } from "@synstack/text"; import { type Llm } from "./llm.types.ts"; +/** + * Creates a user message with support for text, images, and tool responses + * @param template - Template string containing the message content + * @param values - Values to interpolate into the template (text, base64 images, tool responses) + * @returns A strongly-typed user message object conforming to Llm.User.Message + * @example + * ```typescript + * const msg = userMsg`Hello, here's an image: ${base64Image}`; + * const msg2 = userMsg`Tool response: ${toolResponse}`; + * ``` + */ export const userMsg = < T extends Array = Array, @@ -36,6 +47,16 @@ export const userMsg = < }) as Llm.User.Message, ).$; +/** + * Creates an assistant message with support for text and tool calls + * @param template - Template string containing the message content + * @param values - Values to interpolate into the template (text, tool calls) + * @returns A strongly-typed assistant message object conforming to Llm.Assistant.Message + * @example + * ```typescript + * const msg = assistantMsg`Let me help you with that ${toolCall}`; + * ``` + */ export const assistantMsg = < T extends Array = Array, diff --git a/packages/markdown/README.md b/packages/markdown/README.md index 6029d93..0f96819 100644 --- a/packages/markdown/README.md +++ b/packages/markdown/README.md @@ -1 +1,154 @@ -# @synstack/markdown +# @synstack/markdown + +> Type-safe markdown processing with YAML frontmatter support + +This package provides a strongly-typed API for working with markdown documents, including HTML conversion and YAML frontmatter handling with schema validation. + +> [!WARNING] +> This package is included in the [@synstack/synscript](https://github.com/pAIrprogio/synscript) package. It is not recommended to install both packages at the same time. + +## What is it for? + +Working with markdown documents should be type-safe and intuitive. This package provides tools for converting HTML to markdown, managing YAML frontmatter, and handling markdown content: + +```typescript +import { fromHtml, MdDoc } from "@synstack/markdown"; + +// Convert HTML to markdown +const markdown = fromHtml("

Hello

"); +// Returns: "# Hello" + +// Work with markdown documents and frontmatter +interface PostFrontmatter { + title: string; + date: string; +} + +const doc = MdDoc + .withOptions({ schema: postSchema }) + .fromString(`--- +title: Hello World +date: 2024-01-01 +--- +# Content here`); + +// Access typed frontmatter data +console.log(doc.data.title); // "Hello World" + +// Update content while preserving frontmatter +const updated = doc.setBody("# New content"); +``` + +## Installation + +```bash +# Using npm +npm install @synstack/markdown + +# Using yarn +yarn add @synstack/markdown + +# Using pnpm +pnpm add @synstack/markdown +``` + +## Features + +### HTML to Markdown Conversion + +Convert HTML content to markdown with consistent styling: + +```typescript +import { fromHtml } from "@synstack/markdown"; + +const markdown = fromHtml("

Title

Content

"); +// Returns: +// # Title +// +// Content +``` + +### YAML Frontmatter Handling + +Work with YAML frontmatter in markdown documents: + +```typescript +import { getHeaderData, setHeaderData } from "@synstack/markdown"; + +// Extract frontmatter data +const data = getHeaderData("---\ntitle: Hello\n---\n# Content"); +// Returns: { title: "Hello" } + +// Set frontmatter data +const text = setHeaderData("# Content", { title: "Hello" }); +// Returns: "---\ntitle: Hello\n---\n# Content" +``` + +### Type-safe Document Management (MdDoc) + +Handle markdown documents with type-safe frontmatter: + +```typescript +import { MdDoc } from "@synstack/markdown"; + +// Create from markdown string +const doc = MdDoc.fromString(`--- +title: Hello +--- +# Content`); + +// Create from HTML +const htmlDoc = MdDoc.fromHtml("

Title

"); + +// Update content +const updated = doc + .setData({ title: "New Title" }) + .setBody("# Updated content"); +``` + +## API Reference + +### HTML Conversion + +- `fromHtml(html)` - Convert HTML content to markdown + +### Frontmatter Operations + +- `getHeaderData(text, options?)` - Extract and parse YAML frontmatter +- `setHeaderData(text, data, options?)` - Set YAML frontmatter +- `getBody(text)` - Get markdown content without frontmatter +- `setBody(text, body)` - Set markdown content while preserving frontmatter + +### MdDoc Class + +#### Creation +- `MdDoc.withOptions(options)` - Create instance with validation options +- `MdDoc.fromString(text)` - Create from markdown text +- `MdDoc.fromHtml(html)` - Create from HTML content + +#### Properties +- `body` - Get markdown body content +- `data` - Get frontmatter data +- `header` - Get serialized YAML frontmatter +- `options` - Get validation options + +#### Methods +- `fromString(text)` - Create new instance from markdown text +- `fromHtml(html)` - Create new instance from HTML +- `setData(data)` - Update frontmatter data +- `setBody(text)` - Update markdown content +- `toMd()` - Convert to markdown string +- `toString()` - Convert to string (alias for toMd) + +## TypeScript Support + +This package is written in TypeScript and provides comprehensive type definitions: + +- Generic type parameters for frontmatter data +- Zod schema validation for frontmatter +- Type-safe document operations +- Strongly typed HTML conversion + +## License + +Apache-2.0 - see LICENSE file for details. diff --git a/packages/markdown/package.json b/packages/markdown/package.json index 950b69c..3c3b893 100644 --- a/packages/markdown/package.json +++ b/packages/markdown/package.json @@ -5,10 +5,16 @@ "access": "public" }, "version": "1.1.2", - "description": "Opiniated Mardown utils", + "description": "Type-safe markdown processing with YAML frontmatter support and HTML conversion capabilities", "keywords": [ "markdown", - "html" + "html", + "frontmatter", + "yaml", + "type-safe", + "documentation", + "turndown", + "typescript" ], "author": { "name": "pAIrprog", diff --git a/packages/markdown/src/markdown.lib.ts b/packages/markdown/src/markdown.lib.ts index b291d44..ad5a912 100644 --- a/packages/markdown/src/markdown.lib.ts +++ b/packages/markdown/src/markdown.lib.ts @@ -3,6 +3,16 @@ import TurndownService from "turndown"; import { ZodSchema } from "zod"; import { type Stringable } from "../../shared/src/ts.utils.ts"; +/** + * Converts HTML content to Markdown format with consistent styling + * @param html - HTML content to convert + * @returns Markdown formatted string + * @example + * ```typescript + * const markdown = fromHtml("

Hello

"); + * // Returns: "# Hello" + * ``` + */ export const fromHtml = (html: Stringable) => { const turndown = new TurndownService({ headingStyle: "atx", @@ -18,6 +28,17 @@ export const fromHtml = (html: Stringable) => { const HEADER_REGEX = /^---\n([\s\S]*?)\n---\n/; +/** + * Extracts and parses YAML frontmatter from markdown text + * @param text - Markdown text with optional YAML frontmatter + * @param options - Optional schema for validating frontmatter data + * @returns Parsed frontmatter data or undefined if no frontmatter exists + * @example + * ```typescript + * const data = getHeaderData("---\ntitle: Hello\n---\n# Content"); + * // Returns: { title: "Hello" } + * ``` + */ export const getHeaderData = ( text: Stringable, { schema }: { schema?: ZodSchema } = {}, @@ -27,6 +48,18 @@ export const getHeaderData = ( return yaml.deserialize(header, { schema }); }; +/** + * Sets YAML frontmatter in markdown text + * @param text - Markdown text to update + * @param data - Data to serialize as YAML frontmatter + * @param options - Optional schema for validating frontmatter data + * @returns Markdown text with updated frontmatter + * @example + * ```typescript + * const text = setHeaderData("# Content", { title: "Hello" }); + * // Returns: "---\ntitle: Hello\n---\n# Content" + * ``` + */ export const setHeaderData = ( text: Stringable, data: TFormat, @@ -35,15 +68,53 @@ export const setHeaderData = ( return `---\n${yaml.serialize(data, { schema: options.schema })}---\n${getBody(text.toString())}`; }; +/** + * Removes YAML frontmatter from markdown text + * @param text - Markdown text with optional frontmatter + * @returns Markdown text without frontmatter + * @example + * ```typescript + * const body = getBody("---\ntitle: Hello\n---\n# Content"); + * // Returns: "# Content" + * ``` + */ export const getBody = (text: string) => { return text.replace(HEADER_REGEX, ""); }; +/** + * Sets the body content of markdown text while preserving frontmatter + * @param text - Original markdown text with optional frontmatter + * @param body - New body content + * @returns Updated markdown text with preserved frontmatter + * @example + * ```typescript + * const text = setBody("---\ntitle: Hello\n---\nold", "new"); + * // Returns: "---\ntitle: Hello\n---\nnew" + * ``` + */ export const setBody = (text: string, body: string) => { const header = text.match(HEADER_REGEX)?.[0]; return `${header ?? ""}${body}`; }; +/** + * Type-safe markdown document with optional YAML frontmatter + * @template TShape - Shape of the frontmatter data + * @template TData - Type of the current frontmatter data + * @template TBody - Type of the current body content + * @example + * ```typescript + * interface PostFrontmatter { + * title: string; + * date: string; + * } + * + * const doc = MdDoc + * .withOptions({ schema: postSchema }) + * .fromString("---\ntitle: Hello\ndate: 2024-01-01\n---\n# Content"); + * ``` + */ export class MdDoc< TShape = never, TData extends TShape | undefined = never, @@ -63,6 +134,11 @@ export class MdDoc< this._options = options ?? {}; } + /** + * Creates a new MdDoc instance with validation options + * @param options - Schema for validating frontmatter data + * @returns Empty MdDoc instance with specified options + */ public static withOptions( this: void, options: { schema?: ZodSchema }, @@ -74,6 +150,11 @@ export class MdDoc< ); } + /** + * Creates a new MdDoc from markdown text + * @param text - Markdown text with optional frontmatter + * @returns MdDoc instance with parsed frontmatter and body + */ public static fromString(this: void, text: string) { return new MdDoc( getHeaderData(text) as TShape, @@ -81,26 +162,40 @@ export class MdDoc< ); } + /** + * Creates a new MdDoc from HTML content + * @param html - HTML content to convert to markdown + * @returns MdDoc instance with converted markdown body + */ public static fromHtml(this: void, html: string) { return new MdDoc(undefined, fromHtml(html)); } + /** Gets the markdown body content */ public get body(): string { return this._body ?? ""; } + /** Gets the frontmatter data */ public get data(): TData { return this._data; } + /** Gets the serialized YAML frontmatter */ public get header(): string { return this._data ? `---\n${yaml.serialize(this._data)}---\n` : ""; } + /** Gets the validation options */ public get options(): { schema?: ZodSchema } { return this._options; } + /** + * Creates a new MdDoc from markdown text using current options + * @param text - Markdown text with optional frontmatter + * @returns New MdDoc instance with parsed content + */ public fromString(text: string) { const validatedData = getHeaderData(text, { schema: this._options.schema, @@ -112,10 +207,20 @@ export class MdDoc< ); } + /** + * Creates a new MdDoc from HTML content using current options + * @param html - HTML content to convert + * @returns New MdDoc instance with converted content + */ public fromHtml(html: string) { return new MdDoc(this._data, fromHtml(html), this._options); } + /** + * Updates frontmatter data with validation + * @param data - New frontmatter data + * @returns New MdDoc instance with updated data + */ public setData(data: TShape) { const validatedData = this._options.schema ? this._options.schema.parse(data) @@ -123,14 +228,21 @@ export class MdDoc< return new MdDoc(validatedData, this._body, this._options); } + /** + * Updates markdown body content + * @param text - New markdown content + * @returns New MdDoc instance with updated body + */ public setBody(text: string) { return new MdDoc(this._data, text, this._options); } + /** Alias for toString() */ public toMd() { return this.toString(); } + /** Converts document to markdown string with frontmatter */ public toString() { return `${this.header}${this.body}`; } diff --git a/packages/pipe/README.md b/packages/pipe/README.md index a194d54..c2f6b15 100644 --- a/packages/pipe/README.md +++ b/packages/pipe/README.md @@ -1 +1,123 @@ -# @synstack/pipe +# @synstack/pipe + +> Type-safe chainable operations with immutable transformations + +This package provides the foundation for creating chainable, immutable operations in TypeScript. It's used by other @synstack packages to implement type-safe method chaining. + +> [!WARNING] +> This package is included in the [@synstack/synscript](https://github.com/pAIrprogio/synscript) package. It is not recommended to install both packages at the same time. + +## What is it for? + +Create type-safe chainable operations that maintain immutability. Each operation returns a new instance, allowing for safe method chaining: + +```typescript +import { pipe } from '@synstack/pipe' + +// Each operation returns a new instance +const result = pipe('hello') + ._((str) => str.toUpperCase()) // Returns new Pipeable + ._((str) => str.split('')) // Returns new Pipeable + ._((arr) => arr.reverse()) // Returns new Pipeable + .$ // Get final value + +console.log(result) // ['O','L','L','E','H'] + +// Original value remains unchanged +const original = 'hello' +const modified = pipe(original) + ._((str) => str.toUpperCase()) + .$ + +console.log(original) // 'hello' +console.log(modified) // 'HELLO' +``` + +## Installation + +```bash +# Using npm +npm install @synstack/pipe + +# Using yarn +yarn add @synstack/pipe + +# Using pnpm +pnpm add @synstack/pipe +``` + +## Features + +### Immutable Chaining + +The `pipe` function creates a `Pipeable` instance that wraps a value and provides chainable methods: + +```typescript +import { pipe } from '@synstack/pipe' + +// Chain multiple transformations +const result = pipe({ count: 1 }) + ._((obj) => ({ ...obj, doubled: obj.count * 2 })) + ._((obj) => ({ ...obj, squared: obj.doubled ** 2 })) + .$ + +console.log(result) // { count: 1, doubled: 2, squared: 4 } +``` + +### Type-Safe Operations + +TypeScript types are preserved through the chain: + +```typescript +import { pipe } from '@synstack/pipe' + +interface User { + name: string + age: number +} + +const user: User = { name: 'John', age: 30 } + +// Types are inferred correctly through the chain +const result = pipe(user) + ._((u) => ({ ...u, email: 'john@example.com' })) + ._((u) => ({ ...u, isAdult: u.age >= 18 })) + .$ + +// Result type is inferred as: +// { name: string; age: number; email: string; isAdult: boolean } +``` + +## API Reference + +### pipe() + +```typescript +function pipe(value: T): Pipeable +``` + +Creates a new `Pipeable` instance wrapping the provided value. + +### Pipeable Class + +#### Methods + +- `_(fn: (value: T) => U): Pipeable` + - Apply a transformation function + - Returns a new Pipeable instance with the transformed value + +- `$: T` + - Get the current value + - Accessor property that returns the wrapped value + +## TypeScript Support + +This package is written in TypeScript and provides: +- Full type inference through transformation chains +- Type-safe operation composition +- Generic type parameters for maximum flexibility +- IntelliSense support for all operations + +## License + +Apache-2.0 - see LICENSE file for details.