Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 45 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
- [SEO](#seo)
- [Translation](#translation)
- [Using WritrAI Directly](#using-writrai-directly)
- [Migrating to v6](#migrating-to-v6)
- [Unified Processor Engine](#unified-processor-engine)
- [Benchmarks](#benchmarks)
- [ESM and Node Version Support](#esm-and-node-version-support)
Expand Down Expand Up @@ -109,7 +110,6 @@ An example passing in the options also via the constructor:
```javascript
import { Writr, WritrOptions } from 'writr';
const writrOptions = {
throwErrors: true,
renderOptions: {
emoji: true,
toc: true,
Expand All @@ -134,7 +134,6 @@ By default the constructor takes in a markdown `string` or `WritrOptions` in the
```javascript
import { Writr, WritrOptions } from 'writr';
const writrOptions = {
throwErrors: true,
renderOptions: {
emoji: true,
toc: true,
Expand Down Expand Up @@ -183,7 +182,6 @@ Accessing the default options for this instance of Writr. Here is the default se

```javascript
{
throwErrors: false,
renderOptions: {
emoji: true,
toc: true,
Expand Down Expand Up @@ -651,12 +649,12 @@ The following methods emit error events when they fail:
- `validateSync()` - Emits error when validation fails (returns error in result object)

**File Operations:**
- `renderToFile()` - Emits error when file writing fails (does not throw if `throwErrors: false`)
- `renderToFileSync()` - Emits error when file writing fails (does not throw if `throwErrors: false`)
- `loadFromFile()` - Emits error when file reading fails (does not throw if `throwErrors: false`)
- `loadFromFileSync()` - Emits error when file reading fails (does not throw if `throwErrors: false`)
- `saveToFile()` - Emits error when file writing fails (does not throw if `throwErrors: false`)
- `saveToFileSync()` - Emits error when file writing fails (does not throw if `throwErrors: false`)
- `renderToFile()` - Emits error when file writing fails (throws if `throwOnEmitError: true`)
- `renderToFileSync()` - Emits error when file writing fails (throws if `throwOnEmitError: true`)
- `loadFromFile()` - Emits error when file reading fails (throws if `throwOnEmitError: true`)
- `loadFromFileSync()` - Emits error when file reading fails (throws if `throwOnEmitError: true`)
- `saveToFile()` - Emits error when file writing fails (throws if `throwOnEmitError: true`)
- `saveToFileSync()` - Emits error when file writing fails (throws if `throwOnEmitError: true`)

**Front Matter Operations:**
- `frontMatter` getter - Emits error when YAML parsing fails
Expand Down Expand Up @@ -711,7 +709,7 @@ if (!result.valid) {
```javascript
import { Writr } from 'writr';

const writr = new Writr('# Content', { throwErrors: false });
const writr = new Writr('# Content');

writr.on('error', (error) => {
console.error('File operation failed:', error.message);
Expand Down Expand Up @@ -779,11 +777,6 @@ const writr = new Writr('# My Document', {
ai: {
model: openai('gpt-4.1-mini'),
cache: true,
prompts: {
metadata: 'Generate concise metadata focusing on technical accuracy.',
seo: 'Generate SEO metadata optimized for developer documentation.',
translation: 'Translate the document while preserving all code examples.',
},
},
});
```
Expand Down Expand Up @@ -973,9 +966,6 @@ const writr = new Writr('# My Document\n\nSome markdown content here.');
const ai = new WritrAI(writr, {
model: openai('gpt-4.1-mini'),
cache: true,
prompts: {
metadata: 'Generate concise metadata focusing on technical accuracy.',
},
});

// Generate metadata
Expand All @@ -998,6 +988,43 @@ const result = await ai.applyMetadata({
});
```

# Migrating to v6

Writr v6 upgrades [hookified](https://github.com/jaredwray/hookified) from v1 to v2 and removes `throwErrors` in favor of hookified's built-in error handling options.

## Breaking Changes

### `throwErrors` removed

The `throwErrors` option has been removed from `WritrOptions`. Use `throwOnEmitError` instead, which is provided by hookified's `HookifiedOptions` (now spread into `WritrOptions`).

**Before (v5):**

```typescript
const writr = new Writr('# Hello', { throwErrors: true });
```

**After (v6):**

```typescript
const writr = new Writr('# Hello', { throwOnEmitError: true });
```

When `throwOnEmitError` is `true`, file operation methods (`renderToFile`, `loadFromFile`, `saveToFile`, and their sync variants) will throw on errors instead of silently emitting them.

The `frontMatter` getter/setter and `validate()` / `validateSync()` are **not affected** — they continue to emit errors for listeners and return their default values (`{}` and `{ valid: false, error }` respectively), regardless of this setting.

### hookified v2

Writr now uses hookified v2 which introduces several new options available through `WritrOptions`:

- `throwOnEmitError` — Throw when `emit("error")` is called (default: `false`)
- `throwOnHookError` — Throw when a hook handler throws (default: `false`)
- `throwOnEmptyListeners` — Throw when emitting `error` with no listeners (default: `false`)
- `eventLogger` — Logger instance for event logging

See the [hookified documentation](https://github.com/jaredwray/hookified) for full details.

# Unified Processor Engine

Writr builds on top of the open source [unified](https://github.com/unifiedjs/unified) processor – the core project that powers
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "writr",
"version": "5.0.4",
"version": "6.0.0",
"description": "Markdown Rendering Simplified",
"type": "module",
"main": "./dist/writr.js",
Expand Down Expand Up @@ -64,7 +64,7 @@
"ai": "^6.0.116",
"cacheable": "^2.3.3",
"hashery": "^1.5.0",
"hookified": "^1.15.1",
"hookified": "^2.1.0",
"html-react-parser": "^5.2.17",
"js-yaml": "^4.1.1",
"react": "^19.2.4",
Expand Down
9 changes: 7 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import type { LanguageModel } from "ai";
import type { HookifiedOptions } from "hookified";
import type { Writr } from "./writr.js";

/**
* Writr options.
* @typedef {Object} WritrOptions
* @property {RenderOptions} [renderOptions] - Default render options (default: undefined)
* @property {boolean} [throwErrors] - Throw error (default: false)
*/
export type WritrOptions = {
renderOptions?: RenderOptions; // Default render options (default: undefined)
throwErrors?: boolean; // Throw error (default: false)
ai?: WritrAIOptions; // AI options for WritrAI (default: undefined)
};
} & HookifiedOptions;

/**
* Render options.
Expand Down
60 changes: 23 additions & 37 deletions src/writr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export class Writr extends Hookified {
.use(rehypeStringify); // Stringify HTML

private readonly _options: WritrOptions = {
throwErrors: false,
renderOptions: {
emoji: true,
toc: true,
Expand Down Expand Up @@ -88,7 +87,16 @@ export class Writr extends Hookified {
* const writr = new Writr('Hello, world!', {caching: false});
*/
constructor(arguments1?: string | WritrOptions, arguments2?: WritrOptions) {
super();
const options =
typeof arguments1 === "object"
? arguments1
: (arguments2 as WritrOptions | undefined);
super({
throwOnEmitError: options?.throwOnEmitError ?? false,
throwOnHookError: options?.throwOnHookError,
throwOnEmptyListeners: options?.throwOnEmptyListeners ?? false,
eventLogger: options?.eventLogger,
});
if (typeof arguments1 === "string") {
this._content = arguments1;
} else if (arguments1) {
Expand Down Expand Up @@ -209,7 +217,7 @@ export class Writr extends Hookified {
return yaml.load(match[1].trim()) as Record<string, any>;
} catch (error) {
/* v8 ignore next -- @preserve */
this.emit("error", error);
this.emitError(error);
}
}

Expand All @@ -229,7 +237,7 @@ export class Writr extends Hookified {
this._content = this._content.replace(frontMatter, newFrontMatter);
} catch (error) {
/* v8 ignore next -- @preserve */
this.emit("error", error);
this.emitError(error);
}
}

Expand Down Expand Up @@ -377,7 +385,7 @@ export class Writr extends Hookified {

return { valid: true };
} catch (error) {
this.emit("error", error);
this.emitError(error);
if (content !== undefined) {
this._content = originalContent;
}
Expand Down Expand Up @@ -419,7 +427,7 @@ export class Writr extends Hookified {

return { valid: true };
} catch (error) {
this.emit("error", error);
this.emitError(error);
if (content !== undefined) {
this._content = originalContent;
}
Expand Down Expand Up @@ -449,10 +457,6 @@ export class Writr extends Hookified {
await writeFile(data.filePath, data.content);
} catch (error) {
this.emit("error", error);
/* v8 ignore next -- @preserve */
if (this._options.throwErrors) {
throw error;
}
}
}

Expand All @@ -476,11 +480,6 @@ export class Writr extends Hookified {
fs.writeFileSync(data.filePath, data.content);
} catch (error) {
this.emit("error", error);
/* v8 ignore next -- @preserve */
if (this._options.throwErrors) {
/* v8 ignore next -- @preserve */
throw error;
}
}
}

Expand Down Expand Up @@ -540,11 +539,6 @@ export class Writr extends Hookified {
this._content = data.content;
} catch (error) {
this.emit("error", error);
/* v8 ignore next -- @preserve */
if (this._options.throwErrors) {
/* v8 ignore next -- @preserve */
throw error;
}
}
}

Expand All @@ -564,11 +558,6 @@ export class Writr extends Hookified {
this._content = data.content;
} catch (error) {
this.emit("error", error);
/* v8 ignore next -- @preserve */
if (this._options.throwErrors) {
/* v8 ignore next -- @preserve */
throw error;
}
}
}

Expand All @@ -592,11 +581,6 @@ export class Writr extends Hookified {
} catch (error) {
/* v8 ignore next -- @preserve */
this.emit("error", error);
/* v8 ignore next -- @preserve */
if (this._options.throwErrors) {
/* v8 ignore next -- @preserve */
throw error;
}
}
}

Expand All @@ -620,20 +604,15 @@ export class Writr extends Hookified {
} catch (error) {
/* v8 ignore next -- @preserve */
this.emit("error", error);
/* v8 ignore next -- @preserve */
if (this._options.throwErrors) {
/* v8 ignore next -- @preserve */
throw error;
}
}
}

public mergeOptions(
current: WritrOptions,
options: WritrOptions,
): WritrOptions {
if (options.throwErrors !== undefined) {
current.throwErrors = options.throwErrors;
if (options.throwOnEmitError !== undefined) {
this.throwOnEmitError = options.throwOnEmitError;
}

/* v8 ignore next -- @preserve */
Expand All @@ -650,6 +629,13 @@ export class Writr extends Hookified {
return current;
}

private emitError(error: unknown): void {
const current = this.throwOnEmitError;
this.throwOnEmitError = false;
this.emit("error", error);
this.throwOnEmitError = current;
}

private isCacheEnabled(options?: RenderOptions): boolean {
if (options?.caching !== undefined) {
return options.caching;
Expand Down
6 changes: 3 additions & 3 deletions test/writr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("writr", () => {

it("should be able to set options", () => {
const options = {
throwErrors: true,
throwOnEmitError: true,
renderOptions: {
toc: false,
slug: false,
Expand All @@ -33,7 +33,7 @@ describe("writr", () => {
};
const writr = new Writr(options);
expect(writr.options).toBeDefined();
expect(writr.options.throwErrors).toEqual(true);
expect(writr.throwOnEmitError).toEqual(true);
expect(writr.options.renderOptions).toBeInstanceOf(Object);
expect(writr.options.renderOptions?.emoji).toEqual(false);
expect(writr.options.renderOptions?.gfm).toEqual(false);
Expand Down Expand Up @@ -890,7 +890,7 @@ describe("Writr AI Integration", () => {
});

it("should have ai undefined when options are provided without ai", () => {
const writr = new Writr("# Hello World", { throwErrors: true });
const writr = new Writr("# Hello World", { throwOnEmitError: true });
expect(writr.ai).toBeUndefined();
});

Expand Down
Loading