refactor(core): remove implementation details from public API (#2256)

This commit is contained in:
Elijah Potter 2025-11-28 12:36:04 -07:00 committed by GitHub
parent addb5bf441
commit 43e74ebdf3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 473 additions and 363 deletions

View file

@ -1,10 +1,11 @@
use harper_pos_utils::{BurnChunkerCpu, CachedChunker};
use lazy_static::lazy_static;
use std::num::NonZero;
use std::rc::Rc;
use std::sync::Arc;
pub use harper_pos_utils::{BrillChunker, BrillTagger, Chunker, FreqDict, Tagger, UPOS};
pub use harper_pos_utils::{
BrillChunker, BrillTagger, BurnChunkerCpu, CachedChunker, Chunker, FreqDict, Tagger, UPOS,
};
const BRILL_TAGGER_SOURCE: &str = include_str!("../trained_tagger_model.json");
@ -16,6 +17,8 @@ fn uncached_brill_tagger() -> BrillTagger<FreqDict> {
serde_json::from_str(BRILL_TAGGER_SOURCE).unwrap()
}
/// Get a copy of a shared, lazily-initialized [`BrillTagger`]. There will be only one instance
/// per-process.
pub fn brill_tagger() -> Arc<BrillTagger<FreqDict>> {
(*BRILL_TAGGER).clone()
}
@ -30,6 +33,8 @@ fn uncached_brill_chunker() -> BrillChunker {
serde_json::from_str(BRILL_CHUNKER_SOURCE).unwrap()
}
/// Get a copy of a shared, lazily-initialized [`BrillChunker`]. There will be only one instance
/// per-process.
pub fn brill_chunker() -> Arc<BrillChunker> {
(*BRILL_CHUNKER).clone()
}
@ -48,6 +53,9 @@ fn uncached_burn_chunker() -> CachedChunker<BurnChunkerCpu> {
)
}
/// Get a copy of a shared, lazily-initialized [`BurnChunkerCpu`]. There will be only one instance
/// per-process. Since neural net inference is extremely expensive, this chunker is memoized as
/// well.
pub fn burn_chunker() -> Rc<CachedChunker<BurnChunkerCpu>> {
(BURN_CHUNKER).with(|c| c.clone())
}

View file

@ -26,7 +26,7 @@ impl Annotation {
/// Gets an iterator of annotation `Label` from the given document.
///
/// This is similar to [`self::iter_from_document()`], but this additionally converts
/// This is similar to [`Self::iter_from_document`], but this additionally converts
/// the [`Annotation`] into [`ariadne::Label`] for convenience.
pub(super) fn iter_labels_from_document<'inpt_id>(
annotation_type: AnnotationType,

View file

@ -17,8 +17,7 @@ use harper_comments::CommentParser;
use harper_core::linting::LintGroup;
use harper_core::parsers::{Markdown, MarkdownOptions, OrgMode, PlainEnglish};
use harper_core::{
CharStringExt, Dialect, DictWordMetadata, Document, Span, TokenKind, TokenStringExt,
dict_word_metadata_orthography::OrthFlags,
CharStringExt, Dialect, DictWordMetadata, Document, OrthFlags, Span, TokenKind, TokenStringExt,
};
use harper_ink::InkParser;
use harper_literate_haskell::LiterateHaskellParser;

View file

@ -4,8 +4,8 @@
mod char_ext;
mod char_string;
mod currency;
pub mod dict_word_metadata;
pub mod dict_word_metadata_orthography;
mod dict_word_metadata;
mod dict_word_metadata_orthography;
mod document;
mod edit_distance;
pub mod expr;
@ -35,8 +35,8 @@ use std::collections::{BTreeMap, VecDeque};
pub use char_string::{CharString, CharStringExt};
pub use currency::Currency;
pub use dict_word_metadata::{
AdverbData, ConjunctionData, Degree, DeterminerData, Dialect, DictWordMetadata, NounData,
PronounData, VerbData, VerbForm,
AdverbData, ConjunctionData, Degree, DeterminerData, Dialect, DialectFlags, DictWordMetadata,
NounData, PronounData, VerbData, VerbForm, VerbFormFlags,
};
pub use dict_word_metadata_orthography::{OrthFlags, Orthography};
pub use document::Document;
@ -54,7 +54,7 @@ pub use token_kind::TokenKind;
pub use token_string_ext::TokenStringExt;
pub use vec_ext::VecExt;
/// Return harper-core version
/// Return `harper-core` version
pub fn core_version() -> &'static str {
env!("CARGO_PKG_VERSION")
}

View file

@ -1,5 +1,6 @@
use std::collections::VecDeque;
/// Extensions on top of [`Vec`] that make certain common operations easier.
pub trait VecExt {
/// Removes a list of indices from a Vector.
/// Assumes that the provided indices are already in sorted order.

View file

@ -80,10 +80,10 @@
//! - All other token kinds are denoted by their variant name.
use std::borrow::Cow;
use harper_core::dict_word_metadata::VerbFormFlags;
use harper_core::dict_word_metadata_orthography::OrthFlags;
use harper_core::spell::FstDictionary;
use harper_core::{Degree, Dialect, DictWordMetadata, Document, TokenKind};
use harper_core::{
Degree, Dialect, DictWordMetadata, Document, OrthFlags, TokenKind, VerbFormFlags,
};
mod snapshot;

View file

@ -1,4 +1,4 @@
use harper_core::dict_word_metadata::DialectFlags;
use harper_core::DialectFlags;
use itertools::Itertools;
use std::path::Path;

View file

@ -13,6 +13,11 @@ use crate::{
use patch::Patch;
use serde::{Deserialize, Serialize};
/// A [`Chunker`] implementation based on the work by Eric Brill.
///
/// Additional reading:
///
/// - [Continuations on Transformation-based Learning](https://elijahpotter.dev/articles/more-transformation-based-learning)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrillChunker {
base: UPOSFreqDict,

View file

@ -73,6 +73,9 @@ impl<B: Backend> NpModel<B> {
}
}
/// A [`Chunker`] that uses a BiLSTM and the Burn machine learning framework.
///
/// Additional details in this [talk](https://elijahpotter.dev/articles/i-spoke-at-wordcamp-u.s.-in-2025)
pub struct BurnChunker<B: Backend> {
vocab: HashMap<String, usize>,
model: NpModel<B>,

View file

@ -13,6 +13,7 @@ pub use cached_chunker::CachedChunker;
pub use upos_freq_dict::UPOSFreqDict;
/// An implementer of this trait is capable of identifying the noun phrases in a provided sentence.
/// [See here](https://en.wikipedia.org/wiki/Shallow_parsing) for more details on what this is and how it can work.
pub trait Chunker {
/// Iterate over the sentence, identifying the noun phrases contained within.
/// A token marked `true` is a component of a noun phrase.

View file

@ -1,3 +1,5 @@
//! Methods for extracting nominal phrases from datasets.
use std::collections::VecDeque;
use hashbrown::HashSet;

View file

@ -13,6 +13,12 @@ use super::error_counter::{ErrorCounter, ErrorKind};
use crate::{Tagger, UPOS};
/// A [`Tagger`] implementation based on the work by Eric Brill.
///
/// Additional reading:
///
/// - [Brill tagger](https://en.wikipedia.org/wiki/Brill_tagger)
/// - [Transformation-based Learning for POS Tagging](https://elijahpotter.dev/articles/transformation-based-learning)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrillTagger<B>
where

View file

@ -11,6 +11,7 @@ pub use freq_dict::FreqDict;
pub use freq_dict_builder::FreqDictBuilder;
/// An implementer of this trait is capable of assigned Part-of-Speech tags to a provided sentence.
/// This is widely useful for various applications. [See here.](https://en.wikipedia.org/wiki/Part-of-speech_tagging)
pub trait Tagger {
fn tag_sentence(&self, sentence: &[String]) -> Vec<Option<UPOS>>;
}

View file

@ -4,7 +4,7 @@ use std::convert::Into;
use std::io::Cursor;
use std::sync::Arc;
use harper_core::dict_word_metadata::DialectFlags;
use harper_core::DialectFlags;
use harper_core::language_detection::is_doc_likely_english;
use harper_core::linting::{LintGroup, Linter as _};
use harper_core::parsers::{IsolateEnglish, Markdown, Parser, PlainEnglish};
@ -64,6 +64,7 @@ impl Language {
}
}
/// Specifies an English Dialect, often used for linting.
#[wasm_bindgen]
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum Dialect {

View file

@ -367,7 +367,7 @@ async function setActivationKey(key: ActivationKey) {
function initializeLinter(dialect: Dialect) {
linter = new LocalLinter({
binary: new BinaryModule(chrome.runtime.getURL('./wasm/harper_wasm_bg.wasm')),
binary: BinaryModule.create(chrome.runtime.getURL('./wasm/harper_wasm_bg.wasm')),
dialect,
});

View file

@ -49,7 +49,7 @@
"svelte": "^5.41.0",
"svelte-check": "^4.3.3",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.3",
"typescript": "catalog:",
"vite": "^7.1.10"
},
"keywords": [

View file

@ -23,9 +23,10 @@
"api:documenter": "api-documenter markdown -i temp"
},
"devDependencies": {
"@microsoft/api-documenter": "^7.26.10",
"@microsoft/api-extractor": "^7.50.1",
"@microsoft/api-documenter": "^7.28.1",
"@microsoft/api-extractor": "^7.55.1",
"@vitest/browser": "^3.0.6",
"@vitest/ui": "3.0.8",
"harper-wasm": "workspace:*",
"marked": "^16.4.1",
"p-lazy": "^5.0.0",

View file

@ -13,7 +13,7 @@ renderer.link = ({ href, title, text }) => {
href = `${href.slice(0, href.length - 3)}.html`;
}
const titleAttr = title ? ` title="${title}"` : '';
return `<a href="${href}" ${titleAttr}>${text}</a>`;
return `<a href="${href}" ${titleAttr}>${text.replaceAll('\\_', '_')}</a>`;
};
const markdown = fs.readFileSync(input, 'utf8');
@ -30,7 +30,7 @@ const html = `<!doctype html>
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
>
</head>
<body>
<body class="container">
${body}
</body>
</html>

View file

@ -53,13 +53,16 @@ export default interface Linter {
/** Get the linting rule descriptions as an object, formatted in Markdown. */
getLintDescriptions(): Promise<Record<string, string>>;
/** Get the linting rule descriptions as a JSON map, formatted in HTML. */
/** Get the linting rule descriptions as a JSON map, formatted in HTML.
* Wraps the function on the BinaryModule by the same name. */
getLintDescriptionsHTMLAsJSON(): Promise<string>;
/** Get the linting rule descriptions as an object, formatted in HTML */
/** Get the linting rule descriptions as an object, formatted in HTML.
* Wraps the function on the BinaryModule by the same name. */
getLintDescriptionsHTML(): Promise<Record<string, string>>;
/** Convert a string to Chicago-style title case. */
/** Convert a string to Chicago-style title case.
Wraps the function on the BinaryModule by the same name. */
toTitleCase(text: string): Promise<string>;
/** Ignore future instances of a lint from a previous linting run in future invocations. */
@ -110,6 +113,7 @@ export default interface Linter {
importStatsFile(statsFile: string): Promise<void>;
}
/** The properties and information needed to construct a Linter. */
export interface LinterInit {
/** The module or path to the WebAssembly binary. */
binary: BinaryModule;

View file

@ -1,18 +1,20 @@
import type { Dialect, Lint, Suggestion, Linter as WasmLinter } from 'harper-wasm';
import { Language } from 'harper-wasm';
import LazyPromise from 'p-lazy';
import type { BinaryModule } from './binary';
import type { SuperBinaryModule } from './binary';
import type Linter from './Linter';
import type { LinterInit } from './Linter';
import type { LintConfig, LintOptions } from './main';
/** A Linter that runs in the current JavaScript context (meaning it is allowed to block the event loop). */
/** A Linter that runs in the current JavaScript context (meaning it is allowed to block the event loop).
* See the interface definition for more details. */
export default class LocalLinter implements Linter {
binary: BinaryModule;
binary: SuperBinaryModule;
private inner: Promise<WasmLinter>;
constructor(init: LinterInit) {
this.binary = init.binary;
this.binary = init.binary as SuperBinaryModule;
this.binary.setup();
this.inner = this.createInner(init.dialect);
}
@ -73,11 +75,11 @@ export default class LocalLinter implements Linter {
}
async getDefaultLintConfigAsJSON(): Promise<string> {
return this.binary.getDefaultLintConfigAsJSON();
return await this.binary.getDefaultLintConfigAsJSON();
}
async getDefaultLintConfig(): Promise<LintConfig> {
return this.binary.getDefaultLintConfig();
return await this.binary.getDefaultLintConfig();
}
async setLintConfig(config: LintConfig): Promise<void> {
@ -96,7 +98,7 @@ export default class LocalLinter implements Linter {
}
async toTitleCase(text: string): Promise<string> {
return this.binary.toTitleCase(text);
return await this.binary.toTitleCase(text);
}
async getLintDescriptions(): Promise<Record<string, string>> {

View file

@ -0,0 +1,84 @@
import { Span } from 'harper-wasm';
import { beforeEach, describe, expect, test } from 'vitest';
import { binary } from './binary';
import LocalLinter from './LocalLinter';
import Serializer from './Serializer';
describe('Serializer', () => {
let serializer = new Serializer(binary);
beforeEach(() => {
serializer = new Serializer(binary);
});
test('works with strings', async () => {
const start = 'This is a string';
const end = await serializer.deserializeArg(
structuredClone(await serializer.serializeArg(start)),
);
expect(end).toBe(start);
expect(typeof end).toBe(typeof start);
});
test('works with false booleans', async () => {
const start = false;
const end = await serializer.deserializeArg(
structuredClone(await serializer.serializeArg(start)),
);
expect(end).toBe(start);
expect(typeof end).toBe(typeof start);
});
test('works with true booleans', async () => {
const start = true;
const end = await serializer.deserializeArg(
structuredClone(await serializer.serializeArg(start)),
);
expect(end).toBe(start);
expect(typeof end).toBe(typeof start);
});
test('works with numbers', async () => {
const start = 123;
const end = await serializer.deserializeArg(
structuredClone(await serializer.serializeArg(start)),
);
expect(end).toBe(start);
expect(typeof end).toBe(typeof start);
});
test('works with Spans', async () => {
const start = Span.new(123, 321);
const end = await serializer.deserializeArg(
structuredClone(await serializer.serializeArg(start)),
);
expect(end.start).toBe(start.start);
expect(end.len()).toBe(start.len());
expect(typeof end).toBe(typeof start);
});
test('works with Lints', async () => {
const linter = new LocalLinter({ binary });
const lints = await linter.lint('This is an test.');
const start = lints[0];
expect(start).not.toBeNull();
const end = await serializer.deserializeArg(
structuredClone(await serializer.serializeArg(start)),
);
expect(end.message()).toBe(start.message());
expect(end.lint_kind()).toBe(start.lint_kind());
});
});

View file

@ -0,0 +1,152 @@
import type { BinaryModule, SuperBinaryModule } from './binary';
import { assert } from './utils';
export type SerializableTypes =
| 'string'
| 'number'
| 'boolean'
| 'object'
| 'Suggestion'
| 'Lint'
| 'Span'
| 'Array'
| 'undefined'
| 'bigint';
/** Serializable argument to a procedure to be run on the web worker. */
export interface RequestArg {
json: string;
type: SerializableTypes;
}
/** An object that is sent to the web worker to request work to be done. */
export interface SerializedRequest {
/** The procedure to be executed. */
procName: string;
/** The arguments to the procedure */
args: RequestArg[];
}
/** An object that is received by the web worker to request work to be done. */
export interface DeserializedRequest {
/** The procedure to be executed. */
procName: string;
/** The arguments to the procedure */
args: any[];
}
export function isSerializedRequest(v: unknown): v is SerializedRequest {
return typeof v === 'object' && v !== null && 'procName' in v && 'args' in v;
}
/** An internal class that helps the `WorkerLinter` shuffle data across a messaging channel. */
export default class Serializer {
binary: SuperBinaryModule;
constructor(binary: BinaryModule) {
this.binary = binary as SuperBinaryModule;
this.binary.setup();
}
async serializeArg(arg: any): Promise<RequestArg> {
const { Lint, Span, Suggestion } = await this.binary.getBinaryModule();
if (Array.isArray(arg)) {
return {
json: JSON.stringify(await Promise.all(arg.map((a) => this.serializeArg(a)))),
type: 'Array',
};
}
const argType = typeof arg;
switch (argType) {
case 'string':
case 'number':
case 'boolean':
case 'undefined':
return { json: JSON.stringify(arg), type: argType };
case 'bigint':
return { json: arg.toString(), type: argType };
}
if (arg.to_json !== undefined) {
const json = arg.to_json();
let type: SerializableTypes | undefined;
if (arg instanceof Lint) {
type = 'Lint';
} else if (arg instanceof Suggestion) {
type = 'Suggestion';
} else if (arg instanceof Span) {
type = 'Span';
}
if (type === undefined) {
throw new Error('Unhandled case: type undefined');
}
return { json, type };
}
if (argType == 'object') {
return {
json: JSON.stringify(
await Promise.all(
Object.entries(arg).map(([key, value]) => this.serializeArg([key, value])),
),
),
type: 'object',
};
}
throw new Error(`Unhandled case: ${arg}`);
}
async serialize(req: DeserializedRequest): Promise<SerializedRequest> {
return {
procName: req.procName,
args: await Promise.all(req.args.map((arg) => this.serializeArg(arg))),
};
}
async deserializeArg(requestArg: RequestArg): Promise<any> {
const { Lint, Span, Suggestion } = await this.binary.getBinaryModule();
switch (requestArg.type) {
case 'bigint':
return BigInt(requestArg.json);
case 'undefined':
return undefined;
case 'boolean':
case 'number':
case 'string':
return JSON.parse(requestArg.json);
case 'Suggestion':
return Suggestion.from_json(requestArg.json);
case 'Lint':
return Lint.from_json(requestArg.json);
case 'Span':
return Span.from_json(requestArg.json);
case 'Array': {
const parsed = JSON.parse(requestArg.json);
assert(Array.isArray(parsed));
return await Promise.all(parsed.map((arg) => this.deserializeArg(arg)));
}
case 'object': {
const parsed = JSON.parse(requestArg.json);
return Object.fromEntries(
await Promise.all(parsed.map((val: any) => this.deserializeArg(val))),
);
}
default:
throw new Error(`Unhandled case: ${requestArg.type}`);
}
}
async deserialize(request: SerializedRequest): Promise<DeserializedRequest> {
return {
procName: request.procName,
args: await Promise.all(request.args.map((arg) => this.deserializeArg(arg))),
};
}
}

View file

@ -1,5 +1,6 @@
/**
* Represents the summary of linting results.
* Represents the summary of linting results and history.
* Useful to show linting statistics or insights to the user.
*/
export default interface Summary {
/**

View file

@ -1,8 +1,10 @@
import type { Dialect, Lint, Suggestion } from 'harper-wasm';
import type { BinaryModule, DeserializedRequest } from '../binary';
import type { BinaryModule } from '../binary';
import type Linter from '../Linter';
import type { LinterInit } from '../Linter';
import type { LintConfig, LintOptions } from '../main';
import type { DeserializedRequest } from '../Serializer';
import Serializer from '../Serializer';
import Worker from './worker.ts?worker&inline';
/** The data necessary to complete a request once the worker has responded. */
@ -18,6 +20,7 @@ export interface RequestItem {
* NOTE: This class will not work properly in Node. In that case, just use `LocalLinter`. */
export default class WorkerLinter implements Linter {
private binary: BinaryModule;
private serializer: Serializer;
private dialect?: Dialect;
private worker: Worker;
private requestQueue: RequestItem[];
@ -25,6 +28,7 @@ export default class WorkerLinter implements Linter {
constructor(init: LinterInit) {
this.binary = init.binary;
this.serializer = new Serializer(this.binary);
this.dialect = init.dialect;
this.worker = new Worker();
this.requestQueue = [];
@ -43,7 +47,7 @@ export default class WorkerLinter implements Linter {
private setupMainEventListeners() {
this.worker.onmessage = (e: MessageEvent) => {
const { resolve } = this.requestQueue.shift()!;
this.binary.deserializeArg(e.data).then((v) => {
this.serializer.deserializeArg(e.data).then((v) => {
resolve(v);
this.working = false;
@ -209,7 +213,7 @@ export default class WorkerLinter implements Linter {
if (this.requestQueue.length > 0) {
const { request } = this.requestQueue[0];
const serialized = await this.binary.serialize(request);
const serialized = await this.serializer.serialize(request);
this.worker.postMessage(serialized);
} else {
this.working = false;

View file

@ -1,7 +1,8 @@
/// <reference lib="webworker" />
import './shims';
import { BinaryModule, isSerializedRequest, type SerializedRequest } from '../binary';
import { SuperBinaryModule } from '../binary';
import LocalLinter from '../LocalLinter';
import Serializer, { isSerializedRequest, type SerializedRequest } from '../Serializer';
// Notify the main thread that we are ready
self.postMessage('ready');
@ -11,16 +12,17 @@ self.onmessage = (e) => {
if (typeof binaryUrl !== 'string') {
throw new TypeError(`Expected binary to be a string of url but got ${typeof binaryUrl}.`);
}
const binary = new BinaryModule(binaryUrl);
const binary = SuperBinaryModule.create(binaryUrl);
const serializer = new Serializer(binary);
const linter = new LocalLinter({ binary, dialect });
async function processRequest(v: SerializedRequest) {
const { procName, args } = await binary.deserialize(v);
const { procName, args } = await serializer.deserialize(v);
if (procName in linter) {
// @ts-expect-error
const res = await linter[procName](...args);
postMessage(await binary.serializeArg(res));
postMessage(await serializer.serializeArg(res));
}
}

View file

@ -1,63 +0,0 @@
import { Span } from 'harper-wasm';
import { expect, test } from 'vitest';
import { binary } from './binary';
import LocalLinter from './LocalLinter';
test('works with strings', async () => {
const start = 'This is a string';
const end = await binary.deserializeArg(structuredClone(await binary.serializeArg(start)));
expect(end).toBe(start);
expect(typeof end).toBe(typeof start);
});
test('works with false booleans', async () => {
const start = false;
const end = await binary.deserializeArg(structuredClone(await binary.serializeArg(start)));
expect(end).toBe(start);
expect(typeof end).toBe(typeof start);
});
test('works with true booleans', async () => {
const start = true;
const end = await binary.deserializeArg(structuredClone(await binary.serializeArg(start)));
expect(end).toBe(start);
expect(typeof end).toBe(typeof start);
});
test('works with numbers', async () => {
const start = 123;
const end = await binary.deserializeArg(structuredClone(await binary.serializeArg(start)));
expect(end).toBe(start);
expect(typeof end).toBe(typeof start);
});
test('works with Spans', async () => {
const start = Span.new(123, 321);
const end = await binary.deserializeArg(structuredClone(await binary.serializeArg(start)));
expect(end.start).toBe(start.start);
expect(end.len()).toBe(start.len());
expect(typeof end).toBe(typeof start);
});
test('works with Lints', async () => {
const linter = new LocalLinter({ binary });
const lints = await linter.lint('This is an test.');
const start = lints[0];
expect(start).not.toBeNull();
const end = await binary.deserializeArg(structuredClone(await binary.serializeArg(start)));
expect(end.message()).toBe(start.message());
expect(end.lint_kind()).toBe(start.lint_kind());
});

View file

@ -4,9 +4,8 @@ import { default as binaryUrl } from 'harper-wasm/harper_wasm_bg.wasm?no-inline'
import LazyPromise from 'p-lazy';
import pMemoize from 'p-memoize';
import type { LintConfig } from './main';
import { assert } from './utils';
export const loadBinary = pMemoize(async (binary: string) => {
const loadBinary = pMemoize(async (binary: string) => {
const exports = await import('harper-wasm');
let input: InitInput;
@ -26,186 +25,61 @@ export const loadBinary = pMemoize(async (binary: string) => {
return exports;
});
export type SerializableTypes =
| 'string'
| 'number'
| 'boolean'
| 'object'
| 'Suggestion'
| 'Lint'
| 'Span'
| 'Array'
| 'undefined'
| 'bigint';
/** Serializable argument to a procedure to be run on the web worker. */
export interface RequestArg {
json: string;
type: SerializableTypes;
}
/** An object that is sent to the web worker to request work to be done. */
export interface SerializedRequest {
/** The procedure to be executed. */
procName: string;
/** The arguments to the procedure */
args: RequestArg[];
}
/** An object that is received by the web worker to request work to be done. */
export interface DeserializedRequest {
/** The procedure to be executed. */
procName: string;
/** The arguments to the procedure */
args: any[];
}
export function isSerializedRequest(v: unknown): v is SerializedRequest {
return typeof v === 'object' && v !== null && 'procName' in v && 'args' in v;
}
/** This class aims to define the communication protocol between the main thread and the worker.
* Note that much of the complication here comes from the fact that we can't serialize function calls or referenced WebAssembly memory.*/
/** A wrapper around the underlying WebAssembly module that contains Harper's core code. Used to construct a `Linter`, as well as access some miscellaneous other functions. */
export class BinaryModule {
public url: string | URL;
public url: string | URL = '';
private inner: Promise<typeof import('harper-wasm')> | null = null;
private inner: Promise<typeof import('harper-wasm')>;
/** Load a binary from a specified URL. This is the only recommended way to construct this type. */
public static create(url: string | URL): BinaryModule {
const module = new SuperBinaryModule();
constructor(url: string | URL) {
this.url = url;
this.inner = LazyPromise.from(() =>
loadBinary(typeof this.url === 'string' ? this.url : this.url.href),
module.url = url;
module.inner = LazyPromise.from(() =>
loadBinary(typeof module.url === 'string' ? module.url : module.url.href),
);
return module;
}
async getDefaultLintConfigAsJSON(): Promise<string> {
const exported = await this.inner;
public async getDefaultLintConfigAsJSON(): Promise<string> {
const exported = await this.inner!;
return exported.get_default_lint_config_as_json();
}
async getDefaultLintConfig(): Promise<LintConfig> {
const exported = await this.inner;
public async getDefaultLintConfig(): Promise<LintConfig> {
const exported = await this.inner!;
return exported.get_default_lint_config();
}
async toTitleCase(text: string): Promise<string> {
const exported = await this.inner;
public async toTitleCase(text: string): Promise<string> {
const exported = await this.inner!;
return exported.to_title_case(text);
}
async setup(): Promise<void> {
const exported = await this.inner;
public async setup(): Promise<void> {
const exported = await this.inner!;
exported.setup();
}
async createLinter(dialect?: Dialect): Promise<WasmLinter> {
const exported = await this.inner;
return exported.Linter.new(dialect ?? Dialect.American);
}
async serializeArg(arg: any): Promise<RequestArg> {
const { Lint, Span, Suggestion } = await this.inner;
if (Array.isArray(arg)) {
return {
json: JSON.stringify(await Promise.all(arg.map((a) => this.serializeArg(a)))),
type: 'Array',
};
}
const argType = typeof arg;
switch (argType) {
case 'string':
case 'number':
case 'boolean':
case 'undefined':
return { json: JSON.stringify(arg), type: argType };
case 'bigint':
return { json: arg.toString(), type: argType };
}
if (arg.to_json !== undefined) {
const json = arg.to_json();
let type: SerializableTypes | undefined;
if (arg instanceof Lint) {
type = 'Lint';
} else if (arg instanceof Suggestion) {
type = 'Suggestion';
} else if (arg instanceof Span) {
type = 'Span';
}
if (type === undefined) {
throw new Error('Unhandled case: type undefined');
}
return { json, type };
}
if (argType == 'object') {
return {
json: JSON.stringify(
await Promise.all(
Object.entries(arg).map(([key, value]) => this.serializeArg([key, value])),
),
),
type: 'object',
};
}
throw new Error(`Unhandled case: ${arg}`);
}
async serialize(req: DeserializedRequest): Promise<SerializedRequest> {
return {
procName: req.procName,
args: await Promise.all(req.args.map((arg) => this.serializeArg(arg))),
};
}
async deserializeArg(requestArg: RequestArg): Promise<any> {
const { Lint, Span, Suggestion } = await this.inner;
switch (requestArg.type) {
case 'bigint':
return BigInt(requestArg.json);
case 'undefined':
return undefined;
case 'boolean':
case 'number':
case 'string':
return JSON.parse(requestArg.json);
case 'Suggestion':
return Suggestion.from_json(requestArg.json);
case 'Lint':
return Lint.from_json(requestArg.json);
case 'Span':
return Span.from_json(requestArg.json);
case 'Array': {
const parsed = JSON.parse(requestArg.json);
assert(Array.isArray(parsed));
return await Promise.all(parsed.map((arg) => this.deserializeArg(arg)));
}
case 'object': {
const parsed = JSON.parse(requestArg.json);
return Object.fromEntries(
await Promise.all(parsed.map((val: any) => this.deserializeArg(val))),
);
}
default:
throw new Error(`Unhandled case: ${requestArg.type}`);
}
}
async deserialize(request: SerializedRequest): Promise<DeserializedRequest> {
return {
procName: request.procName,
args: await Promise.all(request.args.map((arg) => this.deserializeArg(arg))),
};
}
}
export const binary = /*@__PURE__*/ new BinaryModule(binaryUrl);
export class SuperBinaryModule extends BinaryModule {
async createLinter(dialect?: Dialect): Promise<WasmLinter> {
const exported = await this.getBinaryModule();
return exported.Linter.new(dialect ?? Dialect.American);
}
export const binaryInlined = /*@__PURE__*/ new BinaryModule(binaryInlinedUrl);
async getBinaryModule(): Promise<any> {
return await LazyPromise.from(() =>
loadBinary(typeof this.url === 'string' ? this.url : this.url.href),
);
}
}
/** A version of the Harper WebAssembly binary stored inline as a data URL.
* Can be tree-shaken if unused. */
export const binary = /*@__PURE__*/ BinaryModule.create(binaryUrl);
/** A version of the Harper WebAssembly binary stored inline as a data URL.
* Can be tree-shaken if unused. */
export const binaryInlined = /*@__PURE__*/ BinaryModule.create(binaryInlinedUrl);

View file

@ -4,11 +4,6 @@ export {
BinaryModule,
binary,
binaryInlined,
type DeserializedRequest,
isSerializedRequest,
type RequestArg,
type SerializableTypes,
type SerializedRequest,
} from './binary';
export type { default as Linter, LinterInit } from './Linter';
export { default as LocalLinter } from './LocalLinter';
@ -18,7 +13,7 @@ export { default as WorkerLinter } from './WorkerLinter';
* This is a record, since you shouldn't hard-code the existence of any particular rules and should generalize based on this struct. */
export type LintConfig = Record<string, boolean | null>;
/** The option used to configure the parser for an individual linting operation. */
/** Options available to configure Harper's parser for an individual linting operation. */
export interface LintOptions {
/** The markup language that is being passed. Defaults to `markdown`. */
language?: 'plaintext' | 'markdown';

191
pnpm-lock.yaml generated
View file

@ -16,7 +16,7 @@ catalogs:
specifier: ^2.8.1
version: 2.8.1
typescript:
specifier: ^5.8.2
specifier: ^5.9.3
version: 5.9.3
importers:
@ -169,7 +169,7 @@ importers:
specifier: ^4.1.14
version: 4.1.17
typescript:
specifier: ^5.9.3
specifier: 'catalog:'
version: 5.9.3
vite:
specifier: ^7.1.10
@ -178,14 +178,17 @@ importers:
packages/harper.js:
devDependencies:
'@microsoft/api-documenter':
specifier: ^7.26.10
version: 7.26.17(@types/node@22.13.10)
specifier: ^7.28.1
version: 7.28.1(@types/node@22.13.10)
'@microsoft/api-extractor':
specifier: ^7.50.1
version: 7.52.1(@types/node@22.13.10)
specifier: ^7.55.1
version: 7.55.1(@types/node@22.13.10)
'@vitest/browser':
specifier: ^3.0.6
version: 3.0.8(@testing-library/dom@10.4.0)(@types/node@22.13.10)(playwright@1.52.0)(typescript@5.9.3)(vite@6.3.5(@types/node@22.13.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0))(vitest@3.0.8)
'@vitest/ui':
specifier: 3.0.8
version: 3.0.8(vitest@3.0.8)
harper-wasm:
specifier: workspace:*
version: link:../../harper-wasm/pkg
@ -218,7 +221,7 @@ importers:
version: 0.3.0(vite@6.3.5(@types/node@22.13.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0))
vitest:
specifier: ^3.0.5
version: 3.0.8(@types/debug@4.1.12)(@types/node@22.13.10)(@vitest/browser@3.0.8)(jiti@2.6.1)(jsdom@20.0.3)(lightningcss@1.30.2)(msw@2.7.3(@types/node@22.13.10)(typescript@5.9.3))(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0)
version: 3.0.8(@types/debug@4.1.12)(@types/node@22.13.10)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(jiti@2.6.1)(jsdom@20.0.3)(lightningcss@1.30.2)(msw@2.7.3(@types/node@22.13.10)(typescript@5.9.3))(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0)
packages/harper.js/examples/commonjs-simple:
dependencies:
@ -340,7 +343,7 @@ importers:
version: 6.3.5(@types/node@22.13.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0)
vitest:
specifier: ^3.0.8
version: 3.0.8(@types/debug@4.1.12)(@types/node@22.13.10)(@vitest/browser@3.0.8)(jiti@2.6.1)(jsdom@20.0.3)(lightningcss@1.30.2)(msw@2.7.3(@types/node@22.13.10)(typescript@5.9.3))(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0)
version: 3.0.8(@types/debug@4.1.12)(@types/node@22.13.10)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(jiti@2.6.1)(jsdom@20.0.3)(lightningcss@1.30.2)(msw@2.7.3(@types/node@22.13.10)(typescript@5.9.3))(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0)
packages/vscode-plugin:
dependencies:
@ -2603,22 +2606,22 @@ packages:
'@marijn/find-cluster-break@1.0.2':
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
'@microsoft/api-documenter@7.26.17':
resolution: {integrity: sha512-LOS9U6EJVpejN0vNXFBPdbRW9D2bPJI8FYfLGSaEeiqPfMBlH5QYLKt+OaJomx72uj9Ei6/W49mftK/c+UGPjg==}
'@microsoft/api-documenter@7.28.1':
resolution: {integrity: sha512-mkAss5DhyKtjQ9n+KURxOjz769WhTE376L9D0e6cX/98GBq1fv4SD6AVIOOFxYIsQv+NivskjUhsa+Geq7T2tQ==}
hasBin: true
'@microsoft/api-extractor-model@7.30.4':
resolution: {integrity: sha512-RobC0gyVYsd2Fao9MTKOfTdBm41P/bCMUmzS5mQ7/MoAKEqy0FOBph3JOYdq4X4BsEnMEiSHc+0NUNmdzxCpjA==}
'@microsoft/api-extractor-model@7.32.1':
resolution: {integrity: sha512-u4yJytMYiUAnhcNQcZDTh/tVtlrzKlyKrQnLOV+4Qr/5gV+cpufWzCYAB1Q23URFqD6z2RoL2UYncM9xJVGNKA==}
'@microsoft/api-extractor@7.52.1':
resolution: {integrity: sha512-m3I5uAwE05orsu3D1AGyisX5KxsgVXB+U4bWOOaX/Z7Ftp/2Cy41qsNhO6LPvSxHBaapyser5dVorF1t5M6tig==}
'@microsoft/api-extractor@7.55.1':
resolution: {integrity: sha512-l8Z+8qrLkZFM3HM95Dbpqs6G39fpCa7O5p8A7AkA6hSevxkgwsOlLrEuPv0ADOyj5dI1Af5WVDiwpKG/ya5G3w==}
hasBin: true
'@microsoft/tsdoc-config@0.17.1':
resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==}
'@microsoft/tsdoc-config@0.18.0':
resolution: {integrity: sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==}
'@microsoft/tsdoc@0.15.1':
resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==}
'@microsoft/tsdoc@0.16.0':
resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==}
'@mswjs/interceptors@0.37.6':
resolution: {integrity: sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==}
@ -3307,27 +3310,35 @@ packages:
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
'@rushstack/node-core-library@5.12.0':
resolution: {integrity: sha512-QSwwzgzWoil1SCQse+yCHwlhRxNv2dX9siPnAb9zR/UmMhac4mjMrlMZpk64BlCeOFi1kJKgXRkihSwRMbboAQ==}
'@rushstack/node-core-library@5.19.0':
resolution: {integrity: sha512-BxAopbeWBvNJ6VGiUL+5lbJXywTdsnMeOS8j57Cn/xY10r6sV/gbsTlfYKjzVCUBZATX2eRzJHSMCchsMTGN6A==}
peerDependencies:
'@types/node': '*'
peerDependenciesMeta:
'@types/node':
optional: true
'@rushstack/rig-package@0.5.3':
resolution: {integrity: sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==}
'@rushstack/terminal@0.15.1':
resolution: {integrity: sha512-3vgJYwumcjoDOXU3IxZfd616lqOdmr8Ezj4OWgJZfhmiBK4Nh7eWcv8sU8N/HdzXcuHDXCRGn/6O2Q75QvaZMA==}
'@rushstack/problem-matcher@0.1.1':
resolution: {integrity: sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==}
peerDependencies:
'@types/node': '*'
peerDependenciesMeta:
'@types/node':
optional: true
'@rushstack/ts-command-line@4.23.6':
resolution: {integrity: sha512-7WepygaF3YPEoToh4MAL/mmHkiIImQq3/uAkQX46kVoKTNOOlCtFGyNnze6OYuWw2o9rxsyrHVfIBKxq/am2RA==}
'@rushstack/rig-package@0.6.0':
resolution: {integrity: sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==}
'@rushstack/terminal@0.19.4':
resolution: {integrity: sha512-f4XQk02CrKfrMgyOfhYd3qWI944dLC21S4I/LUhrlAP23GTMDNG6EK5effQtFkISwUKCgD9vMBrJZaPSUquxWQ==}
peerDependencies:
'@types/node': '*'
peerDependenciesMeta:
'@types/node':
optional: true
'@rushstack/ts-command-line@5.1.4':
resolution: {integrity: sha512-H0I6VdJ6sOUbktDFpP2VW5N29w8v4hRoNZOQz02vtEi6ZTYL1Ju8u+TcFiFawUDrUsx/5MQTUhd79uwZZVwVlA==}
'@sec-ant/readable-stream@0.4.1':
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
@ -4321,6 +4332,11 @@ packages:
'@vitest/spy@3.0.8':
resolution: {integrity: sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==}
'@vitest/ui@3.0.8':
resolution: {integrity: sha512-MfTjaLU+Gw/lYorgwFZ06Cym+Mj9hPfZh/Q91d4JxyAHiicAakPTvS7zYCSHF+5cErwu2PVBe1alSjuh6L/UiA==}
peerDependencies:
vitest: 3.0.8
'@vitest/utils@3.0.8':
resolution: {integrity: sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==}
@ -6178,6 +6194,10 @@ packages:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
diff@8.0.2:
resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==}
engines: {node: '>=0.3.1'}
dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@ -6867,6 +6887,9 @@ packages:
fflate@0.4.8:
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
file-entry-cache@10.0.7:
resolution: {integrity: sha512-txsf5fu3anp2ff3+gOJJzRImtrtm/oa9tYLN0iTuINZ++EyVR/nRrg2fKYwvG/pXDofcrvvb0scEbX3NyW/COw==}
@ -8165,10 +8188,6 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@3.13.1:
resolution: {integrity: sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==}
hasBin: true
js-yaml@3.14.1:
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
hasBin: true
@ -11115,8 +11134,8 @@ packages:
third-party-web@0.26.5:
resolution: {integrity: sha512-tDuKQJUTfjvi9Fcrs1s6YAQAB9mzhTSbBZMfNgtWNmJlHuoFeXO6dzBFdGeCWRvYL50jQGK0jPsBZYxqZQJ2SA==}
third-party-web@0.28.0:
resolution: {integrity: sha512-4P798O67JmIKRJfJ1HSOkIsZrx2+FuaN2jTQX+imHXFPbGp17KSMDabYxrRT011B3gBzaoHFhUkBlEkNZN8vuQ==}
third-party-web@0.29.0:
resolution: {integrity: sha512-nBDSJw5B7Sl1YfsATG2XkW5qgUPODbJhXw++BKygi9w6O/NKS98/uY/nR/DxDq2axEjL6halHW1v+jhm/j1DBQ==}
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
@ -11133,10 +11152,6 @@ packages:
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
tinyglobby@0.2.12:
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
engines: {node: '>=12.0.0'}
tinyglobby@0.2.13:
resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
engines: {node: '>=12.0.0'}
@ -15054,37 +15069,38 @@ snapshots:
'@marijn/find-cluster-break@1.0.2': {}
'@microsoft/api-documenter@7.26.17(@types/node@22.13.10)':
'@microsoft/api-documenter@7.28.1(@types/node@22.13.10)':
dependencies:
'@microsoft/api-extractor-model': 7.30.4(@types/node@22.13.10)
'@microsoft/tsdoc': 0.15.1
'@rushstack/node-core-library': 5.12.0(@types/node@22.13.10)
'@rushstack/terminal': 0.15.1(@types/node@22.13.10)
'@rushstack/ts-command-line': 4.23.6(@types/node@22.13.10)
js-yaml: 3.13.1
'@microsoft/api-extractor-model': 7.32.1(@types/node@22.13.10)
'@microsoft/tsdoc': 0.16.0
'@rushstack/node-core-library': 5.19.0(@types/node@22.13.10)
'@rushstack/terminal': 0.19.4(@types/node@22.13.10)
'@rushstack/ts-command-line': 5.1.4(@types/node@22.13.10)
js-yaml: 4.1.0
resolve: 1.22.10
transitivePeerDependencies:
- '@types/node'
'@microsoft/api-extractor-model@7.30.4(@types/node@22.13.10)':
'@microsoft/api-extractor-model@7.32.1(@types/node@22.13.10)':
dependencies:
'@microsoft/tsdoc': 0.15.1
'@microsoft/tsdoc-config': 0.17.1
'@rushstack/node-core-library': 5.12.0(@types/node@22.13.10)
'@microsoft/tsdoc': 0.16.0
'@microsoft/tsdoc-config': 0.18.0
'@rushstack/node-core-library': 5.19.0(@types/node@22.13.10)
transitivePeerDependencies:
- '@types/node'
'@microsoft/api-extractor@7.52.1(@types/node@22.13.10)':
'@microsoft/api-extractor@7.55.1(@types/node@22.13.10)':
dependencies:
'@microsoft/api-extractor-model': 7.30.4(@types/node@22.13.10)
'@microsoft/tsdoc': 0.15.1
'@microsoft/tsdoc-config': 0.17.1
'@rushstack/node-core-library': 5.12.0(@types/node@22.13.10)
'@rushstack/rig-package': 0.5.3
'@rushstack/terminal': 0.15.1(@types/node@22.13.10)
'@rushstack/ts-command-line': 4.23.6(@types/node@22.13.10)
'@microsoft/api-extractor-model': 7.32.1(@types/node@22.13.10)
'@microsoft/tsdoc': 0.16.0
'@microsoft/tsdoc-config': 0.18.0
'@rushstack/node-core-library': 5.19.0(@types/node@22.13.10)
'@rushstack/rig-package': 0.6.0
'@rushstack/terminal': 0.19.4(@types/node@22.13.10)
'@rushstack/ts-command-line': 5.1.4(@types/node@22.13.10)
diff: 8.0.2
lodash: 4.17.21
minimatch: 3.0.8
minimatch: 10.0.3
resolve: 1.22.10
semver: 7.5.4
source-map: 0.6.1
@ -15092,14 +15108,14 @@ snapshots:
transitivePeerDependencies:
- '@types/node'
'@microsoft/tsdoc-config@0.17.1':
'@microsoft/tsdoc-config@0.18.0':
dependencies:
'@microsoft/tsdoc': 0.15.1
'@microsoft/tsdoc': 0.16.0
ajv: 8.12.0
jju: 1.4.0
resolve: 1.22.10
'@microsoft/tsdoc@0.15.1': {}
'@microsoft/tsdoc@0.16.0': {}
'@mswjs/interceptors@0.37.6':
dependencies:
@ -15200,7 +15216,7 @@ snapshots:
'@paulirish/trace_engine@0.0.44':
dependencies:
third-party-web: 0.28.0
third-party-web: 0.29.0
'@php-wasm/node-polyfills@0.6.16': {}
@ -15712,7 +15728,7 @@ snapshots:
'@rtsao/scc@1.1.0': {}
'@rushstack/node-core-library@5.12.0(@types/node@22.13.10)':
'@rushstack/node-core-library@5.19.0(@types/node@22.13.10)':
dependencies:
ajv: 8.13.0
ajv-draft-04: 1.0.0(ajv@8.13.0)
@ -15725,21 +15741,26 @@ snapshots:
optionalDependencies:
'@types/node': 22.13.10
'@rushstack/rig-package@0.5.3':
'@rushstack/problem-matcher@0.1.1(@types/node@22.13.10)':
optionalDependencies:
'@types/node': 22.13.10
'@rushstack/rig-package@0.6.0':
dependencies:
resolve: 1.22.10
strip-json-comments: 3.1.1
'@rushstack/terminal@0.15.1(@types/node@22.13.10)':
'@rushstack/terminal@0.19.4(@types/node@22.13.10)':
dependencies:
'@rushstack/node-core-library': 5.12.0(@types/node@22.13.10)
'@rushstack/node-core-library': 5.19.0(@types/node@22.13.10)
'@rushstack/problem-matcher': 0.1.1(@types/node@22.13.10)
supports-color: 8.1.1
optionalDependencies:
'@types/node': 22.13.10
'@rushstack/ts-command-line@4.23.6(@types/node@22.13.10)':
'@rushstack/ts-command-line@5.1.4(@types/node@22.13.10)':
dependencies:
'@rushstack/terminal': 0.15.1(@types/node@22.13.10)
'@rushstack/terminal': 0.19.4(@types/node@22.13.10)
'@types/argparse': 1.0.38
argparse: 1.0.10
string-argv: 0.3.2
@ -16963,7 +16984,7 @@ snapshots:
dependencies:
'@sveltejs/kit': 2.48.5(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.43.12)(vite@6.3.5(@types/node@22.13.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0)))(svelte@5.43.12)(vite@6.3.5(@types/node@22.13.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0))
kolorist: 1.8.0
tinyglobby: 0.2.12
tinyglobby: 0.2.15
vite-plugin-pwa: 0.21.1(vite@6.3.5(@types/node@22.13.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0))(workbox-build@7.3.0(@types/babel__core@7.20.5))(workbox-window@7.3.0)
'@vitest/browser@3.0.8(@testing-library/dom@10.4.0)(@types/node@22.13.10)(playwright@1.52.0)(typescript@5.9.3)(vite@6.3.5(@types/node@22.13.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0))(vitest@3.0.8)':
@ -16975,7 +16996,7 @@ snapshots:
msw: 2.7.3(@types/node@22.13.10)(typescript@5.9.3)
sirv: 3.0.1
tinyrainbow: 2.0.0
vitest: 3.0.8(@types/debug@4.1.12)(@types/node@22.13.10)(@vitest/browser@3.0.8)(jiti@2.6.1)(jsdom@20.0.3)(lightningcss@1.30.2)(msw@2.7.3(@types/node@22.13.10)(typescript@5.9.3))(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0)
vitest: 3.0.8(@types/debug@4.1.12)(@types/node@22.13.10)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(jiti@2.6.1)(jsdom@20.0.3)(lightningcss@1.30.2)(msw@2.7.3(@types/node@22.13.10)(typescript@5.9.3))(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0)
ws: 8.18.1
optionalDependencies:
playwright: 1.52.0
@ -17022,6 +17043,17 @@ snapshots:
dependencies:
tinyspy: 3.0.2
'@vitest/ui@3.0.8(vitest@3.0.8)':
dependencies:
'@vitest/utils': 3.0.8
fflate: 0.8.2
flatted: 3.3.3
pathe: 2.0.3
sirv: 3.0.1
tinyglobby: 0.2.15
tinyrainbow: 2.0.0
vitest: 3.0.8(@types/debug@4.1.12)(@types/node@22.13.10)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(jiti@2.6.1)(jsdom@20.0.3)(lightningcss@1.30.2)(msw@2.7.3(@types/node@22.13.10)(typescript@5.9.3))(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0)
'@vitest/utils@3.0.8':
dependencies:
'@vitest/pretty-format': 3.0.8
@ -19816,6 +19848,8 @@ snapshots:
diff@4.0.2: {}
diff@8.0.2: {}
dir-glob@3.0.1:
dependencies:
path-type: 4.0.0
@ -20707,6 +20741,8 @@ snapshots:
fflate@0.4.8: {}
fflate@0.8.2: {}
file-entry-cache@10.0.7:
dependencies:
flat-cache: 6.1.7
@ -22306,11 +22342,6 @@ snapshots:
js-tokens@4.0.0: {}
js-yaml@3.13.1:
dependencies:
argparse: 1.0.10
esprima: 4.0.1
js-yaml@3.14.1:
dependencies:
argparse: 1.0.10
@ -25765,7 +25796,7 @@ snapshots:
third-party-web@0.26.5: {}
third-party-web@0.28.0: {}
third-party-web@0.29.0: {}
through@2.3.8: {}
@ -25777,11 +25808,6 @@ snapshots:
tinyexec@0.3.2: {}
tinyglobby@0.2.12:
dependencies:
fdir: 6.4.3(picomatch@4.0.2)
picomatch: 4.0.2
tinyglobby@0.2.13:
dependencies:
fdir: 6.4.4(picomatch@4.0.2)
@ -26293,7 +26319,7 @@ snapshots:
vite-plugin-dts@4.5.3(@types/node@22.13.10)(rollup@4.53.3)(typescript@5.9.3)(vite@6.3.5(@types/node@22.13.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0)):
dependencies:
'@microsoft/api-extractor': 7.52.1(@types/node@22.13.10)
'@microsoft/api-extractor': 7.55.1(@types/node@22.13.10)
'@rollup/pluginutils': 5.1.4(rollup@4.53.3)
'@volar/typescript': 2.4.12
'@vue/language-core': 2.2.0(typescript@5.9.3)
@ -26415,7 +26441,7 @@ snapshots:
optionalDependencies:
vite: 7.2.2(@types/node@22.13.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0)
vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.13.10)(@vitest/browser@3.0.8)(jiti@2.6.1)(jsdom@20.0.3)(lightningcss@1.30.2)(msw@2.7.3(@types/node@22.13.10)(typescript@5.9.3))(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0):
vitest@3.0.8(@types/debug@4.1.12)(@types/node@22.13.10)(@vitest/browser@3.0.8)(@vitest/ui@3.0.8)(jiti@2.6.1)(jsdom@20.0.3)(lightningcss@1.30.2)(msw@2.7.3(@types/node@22.13.10)(typescript@5.9.3))(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0):
dependencies:
'@vitest/expect': 3.0.8
'@vitest/mocker': 3.0.8(msw@2.7.3(@types/node@22.13.10)(typescript@5.9.3))(vite@6.3.5(@types/node@22.13.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0))
@ -26441,6 +26467,7 @@ snapshots:
'@types/debug': 4.1.12
'@types/node': 22.13.10
'@vitest/browser': 3.0.8(@testing-library/dom@10.4.0)(@types/node@22.13.10)(playwright@1.52.0)(typescript@5.9.3)(vite@6.3.5(@types/node@22.13.10)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.7.0))(vitest@3.0.8)
'@vitest/ui': 3.0.8(vitest@3.0.8)
jsdom: 20.0.3
transitivePeerDependencies:
- jiti

View file

@ -3,19 +3,19 @@ packages:
- harper-wasm/pkg
- packages/harper.js/examples/*
catalog:
typescript: ^5.8.2
typescript: ^5.9.3
tslib: ^2.8.1
'@types/node': ^22.13.10
'@babel/runtime': ^7.26.10
"@types/node": ^22.13.10
"@babel/runtime": ^7.26.10
onlyBuiltDependencies:
- '@biomejs/biome'
- '@parcel/watcher'
- '@swc/core'
- '@vscode/vsce-sign'
- "@biomejs/biome"
- "@parcel/watcher"
- "@swc/core"
- "@vscode/vsce-sign"
- core-js
- core-js-pure
- esbuild
- keytar
- msw
- svelte-preprocess
- '@tailwindcss/oxide'
- "@tailwindcss/oxide"