diff --git a/ts/README.md b/ts/README.md
new file mode 100644
index 0000000..bc08d3b
--- /dev/null
+++ b/ts/README.md
@@ -0,0 +1,355 @@
+# āļø LangDiff: Progressive UI from LLM
+
+[](https://www.npmjs.com/package/@langdiff/langdiff)
+[](https://github.com/globalaiplatform/langdiff/blob/main/LICENSE)
+[](https://globalaiplatform.com/)
+
+LangDiff is a TypeScript library that solves the hard problems of streaming structured LLM outputs to frontends.
+
+
+
+LangDiff provides intelligent partial parsing with granular, type-safe events as JSON structures build token by token, plus automatic JSON Patch generation for efficient frontend synchronization. Build responsive AI applications where your backend structures and frontend experiences can evolve independently. Read more about it on the [Motivation](#motivation) section.
+
+## Demo
+
+Click the image below.
+
+[
](https://globalaiplatform.github.io/langdiff/)
+
+
+## Core Features
+
+### Streaming Parsing
+- Define schemas for streaming structured outputs using class-based models.
+- Receive granular, type-safe callbacks (`onAppend`, `onUpdate`, `onComplete`) as tokens stream in.
+- Full TypeScript support for type safety and IntelliSense.
+
+
+
+| Without LangDiff | With LangDiff |
+
+
+|
+
+```typescript
+parsePartial('{"it')
+parsePartial('{"items":')
+parsePartial('{"items": ["Buy a b')
+parsePartial('{"items": ["Buy a banana", "')
+parsePartial('{"items": ["Buy a banana", "Pack b')
+parsePartial('{"items": ["Buy a banana", "Pack bags"]}')
+```
+
+ |
+
+
+```typescript
+onItemListAppend("", index=0)
+onItemAppend("Buy a b")
+onItemAppend("anana")
+onItemListAppend("", index=1)
+onItemAppend("Pack b")
+onItemAppend("ags")
+```
+
+ |
+
+
+
+### Change Tracking
+- Track mutations without changing your code patterns by instrumenting existing objects and arrays.
+- Generate JSON Patch diffs automatically for efficient state synchronization between frontend and backend.
+
+
+
+| Without LangDiff | With LangDiff |
+
+
+|
+
+```http
+data: {"it
+data: ems":
+data: ["Buy a b
+data: anana", "
+data: Pack b
+data: ags"]}
+```
+
+ |
+
+
+```http
+data: {"op": "add", "path": "/items/-", "value": "Buy a b"}
+data: {"op": "append", "path": "/items/0", "value": "anana"}
+data: {"op": "add", "path": "/items/-", "value": "Pack b"}
+data: {"op": "append", "path": "/items/1", "value": "ags"}
+```
+
+ |
+
+
+
+## Usage
+
+### Installation
+
+```bash
+npm install @langdiff/langdiff
+```
+
+For yarn:
+
+```bash
+yarn add @langdiff/langdiff
+```
+
+### Streaming Parsing
+
+Suppose you want to generate a multi-section article with an LLM. Rather than waiting for the entire response,
+you can stream the article progressively by first generating section titles as they're determined,
+then streaming each section's content as it's written.
+
+
+
+Start by defining model classes that specify your streaming structure:
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+
+const ArticleGenerationResponse = ld.object({
+ sectionTitles: ld.array(ld.string()),
+ sectionContents: ld.array(ld.string())
+});
+```
+
+The streaming classes handle internal streaming progression automatically.
+Create an instance and attach event handlers to respond to streaming events:
+
+```typescript
+interface Section {
+ title: string;
+ content: string;
+ done: boolean;
+}
+
+interface Article {
+ sections: Section[];
+}
+
+const ui: Article = { sections: [] };
+const response = ArticleGenerationResponse.create();
+
+response.sectionTitles.onAppend((title, index) => {
+ ui.sections.push({ title: '', content: '', done: false });
+
+ title.onAppend((chunk) => {
+ ui.sections[index].title += chunk;
+ });
+});
+
+response.sectionContents.onAppend((content, index) => {
+ if (index >= ui.sections.length) {
+ return;
+ }
+
+ content.onAppend((chunk) => {
+ ui.sections[index].content += chunk;
+ });
+
+ content.onComplete(() => {
+ ui.sections[index].done = true;
+ });
+});
+```
+
+Create a streaming parser with `Parser` and feed token chunks from your LLM stream (`push()`):
+
+```typescript
+import OpenAI from 'openai';
+import { zodResponseFormat } from 'openai/helpers/zod';
+import { Parser } from '@langdiff/langdiff';
+
+const client = new OpenAI();
+
+const stream = await client.chat.completions.create({
+ model: "gpt-4",
+ messages: [{ role: "user", content: "Write me a guide to open source a TypeScript library." }],
+ stream: true,
+ response_format: zodResponseFormat(ArticleGenerationResponse.toZod(), 'ArticleGenerationResponse'),
+});
+
+const parser = new Parser(response);
+
+for await (const chunk of stream) {
+ const content = chunk.choices[0]?.delta?.content;
+ if (content) {
+ parser.push(content);
+ console.log(ui);
+ }
+}
+
+parser.complete();
+console.log(ui);
+```
+
+### Change Tracking
+
+To automatically track changes to your `Article` object, wrap it with `trackChange()`:
+
+```diff
+- const ui: Article = { sections: [] };
++ const [ui, diffBuf] = trackChange({ sections: [] });
+```
+
+Now all modifications to `ui` and its nested objects are automatically captured in `diffBuf`.
+
+Access the accumulated changes using `diffBuf.flush()`:
+
+```typescript
+import OpenAI from 'openai';
+import { Parser, trackChange } from '@langdiff/langdiff';
+
+const client = new OpenAI();
+
+const stream = await client.chat.completions.create({
+ // ... same config as above
+ stream: true,
+});
+
+const parser = new Parser(response);
+
+for await (const chunk of stream) {
+ const content = chunk.choices[0]?.delta?.content;
+ if (content) {
+ parser.push(content);
+ console.log(diffBuf.flush()); // Array of JSON Patch objects
+ }
+}
+
+parser.complete();
+console.log(diffBuf.flush());
+
+// Output:
+// [{"op": "add", "path": "/sections/-", "value": {"title": "", "content": "", "done": false}}]
+// [{"op": "append", "path": "/sections/0/title", "value": "Abs"}]
+// [{"op": "append", "path": "/sections/0/title", "value": "tract"}]
+// ...
+```
+
+Notes:
+
+- `flush()` returns and clears the accumulated changes, so each call gives you only new modifications
+- Send these lightweight diffs to your frontend instead of retransmitting entire objects
+- Diffs use JSON Patch format ([RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902)) with an additional `append` operation for efficient string building
+- For standard JSON Patch compatibility, use `trackChange(obj, JSONPatchChangeTracker)`
+
+## Motivation
+
+Modern AI applications increasingly rely on LLMs to generate structured data rather than just conversational text. While LLM providers offer structured output capabilities (like OpenAI's JSON mode), streaming these outputs poses unique challenges that existing tools don't adequately address.
+
+### The Problem with Traditional Streaming Approaches
+
+When LLMs generate complex JSON structures, waiting for the complete response creates poor user experiences. Standard streaming JSON parsers can't handle incomplete tokens - for example, `{"sentence": "Hello,` remains unparseable until the closing quote arrives. This means users see nothing until substantial chunks complete, defeating the purpose of streaming.
+
+Even partial JSON parsing libraries that "repair" incomplete JSON don't fully solve the issues:
+- **No type safety**: You lose static type checking when dealing with partial objects
+- **No granular control**: Can't distinguish between complete and incomplete fields
+
+### The Coupling Problem
+
+A more fundamental issue emerges in production applications: tightly coupling frontend UIs to LLM output schemas. When you stream raw JSON chunks from backend to frontend, several problems arise:
+
+**Schema Evolution**: Improving prompts often requires changing JSON schemas. If your frontend directly consumes LLM output, every schema change may cause a breaking change.
+
+**Backward Compatibility**: Consider a restaurant review summarizer that originally outputs:
+```json
+{"summary": ["Food is great", "Nice interior"]}
+```
+
+Adding emoji support requires a new schema:
+```json
+{"summaryV2": [{"emoji": "š½ļø", "text": "Food is great"}]}
+```
+
+Supporting both versions in a single LLM output creates inefficiencies and synchronization issues between the redundant fields.
+
+**Implementation Detail Leakage**: Frontend code becomes dependent on LLM provider specifics, prompt engineering decisions, and token streaming patterns.
+
+### The LangDiff Approach
+
+LangDiff solves these problems through two key innovations:
+
+1. **Intelligent Streaming Parsing**: Define schemas that understand the streaming nature of LLM outputs. Get type-safe callbacks for partial updates, complete fields, and new array items as they arrive.
+2. **Change-Based Synchronization**: Instead of streaming raw JSON, track mutations on your application objects and send lightweight JSON Patch diffs to frontends. This decouples UI state from LLM output format.
+
+This architecture allows:
+- **Independent Evolution**: Change LLM prompts and schemas without breaking frontends
+- **Efficient Updates**: Send only what changed, not entire objects
+- **Type Safety**: Maintain static type checking throughout the streaming process
+
+LangDiff enables you to build responsive, maintainable AI applications where the backend prompt engineering and frontend user experience can evolve independently.
+
+## License
+
+Apache-2.0. See the [LICENSE](/LICENSE) file for details.
+
+## Examples
+
+This repository includes comprehensive examples demonstrating various LangDiff features:
+
+### Quick Start Examples
+
+```bash
+# Run all examples
+npm run examples
+
+# Or run individual examples
+npm run examples:basic # Basic streaming parser
+npm run examples:tracking # Change tracking
+npm run examples:openai # OpenAI integration
+npm run examples:frontend # Frontend integration
+npm run examples:article # Article generation (comprehensive)
+```
+
+### Available Examples
+
+1. **Basic Streaming** (`examples/01-basic-streaming.ts`)
+ - Fundamental streaming parser usage
+ - Event handlers and callbacks
+ - Simple shopping list demo
+
+2. **Change Tracking** (`examples/02-change-tracking.ts`)
+ - Object mutation tracking
+ - JSON Patch generation
+ - State synchronization patterns
+
+3. **OpenAI Integration** (`examples/03-openai-integration.ts`)
+ - Real-world AI application example
+ - Streaming LLM responses
+ - Code review assistant demo
+
+4. **Frontend Integration** (`examples/04-frontend-integration.ts`)
+ - Server-Sent Events patterns
+ - React and Vue integration examples
+ - Real-time chat application demo
+
+5. **Article Generation** (`examples/05-article-generation.ts`)
+ - Complete article generation scenario
+ - Combined streaming parsing and change tracking
+ - Real-time console UI with visual feedback
+
+### Running Examples
+
+```bash
+# Install dependencies
+npm install
+
+# Run a specific example
+npx ts-node examples/01-basic-streaming.ts
+
+# Or with the full article demo
+npx ts-node examples/05-article-generation.ts
+```
+
+See the [`examples/`](/ts/examples/) directory for detailed implementations and the [`examples/README.md`](/ts/examples/README.md) for comprehensive documentation.
diff --git a/ts/docs/api/overview.md b/ts/docs/api/overview.md
new file mode 100644
index 0000000..7ca154c
--- /dev/null
+++ b/ts/docs/api/overview.md
@@ -0,0 +1,98 @@
+# API Reference Overview
+
+LangDiff provides two main modules for streaming structured data and tracking changes:
+
+## Parser Module
+
+The parser module contains streaming-aware data types and the core parser for processing token streams.
+
+### Core Classes
+
+- **[`StreamingObject`](parser.md#StreamingObject)** - Base class for streaming JSON objects
+- **[`StreamingList`](parser.md#StreamingList)** - Represents a streaming JSON array
+- **[`StreamingString`](parser.md#StreamingString)** - Represents a streaming string value
+- **[`Atom`](parser.md#Atom)** - Represents atomic values (numbers, booleans, null)
+- **[`Parser`](parser.md#Parser)** - Processes token streams and triggers callbacks
+
+### Schema Builder Functions
+
+- **[`string()`](parser.md#string)** - Create a streaming string schema
+- **[`array(itemSchema)`](parser.md#array)** - Create a streaming array schema
+- **[`object(fields)`](parser.md#object)** - Create a streaming object schema
+- **[`number()`](parser.md#number)** - Create a number atom schema
+- **[`boolean()`](parser.md#boolean)** - Create a boolean atom schema
+- **[`atom(zodSchema)`](parser.md#atom)** - Create a custom atom schema
+- **[`fromZod(zodSchema)`](parser.md#fromZod)** - Create streaming schema from Zod schema
+
+### Type Aliases
+
+- **`String`** - Alias for `StreamingString`
+- **`Object`** - Alias for `StreamingObject`
+
+### Key Features
+
+- **Event Callbacks**: All streaming types support `onStart`, `onAppend`, and `onComplete` callbacks
+- **Type Safety**: Full TypeScript generics and interfaces for compile-time checking
+- **Zod Integration**: Convert streaming schemas to Zod schemas via `toZod()` and create from Zod schemas via `fromZod()`
+
+## Tracker Module
+
+The tracker module provides change tracking capabilities for generating JSON Patch diffs.
+
+### Core Classes
+
+- **[`ChangeTracker`](tracker.md#ChangeTracker)** - Abstract base for change tracking
+- **[`JSONPatchChangeTracker`](tracker.md#JSONPatchChangeTracker)** - Standard JSON Patch tracking
+- **[`EfficientJSONPatchChangeTracker`](tracker.md#EfficientJSONPatchChangeTracker)** - Enhanced tracking with `append` operations
+
+### Utility Functions
+
+- **[`trackChange()`](tracker.md#trackChange)** - Wrap objects for automatic change tracking
+- **[`applyChange()`](tracker.md#applyChange)** - Apply JSON Patch operations to objects
+
+## Usage Patterns
+
+### Basic Streaming
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+
+// Define schema using the modern functional API
+const Response = ld.object({
+ title: ld.string(),
+ items: ld.array(ld.string())
+});
+
+// Set up callbacks
+const response = Response.create();
+
+response.title.onAppend((chunk: string) => {
+ console.log(`Title: ${chunk}`);
+});
+
+// Parse stream
+const parser = new ld.Parser(response);
+for (const chunk of streamChunks) {
+ parser.push(chunk);
+}
+parser.complete();
+```
+
+### Change Tracking
+
+```typescript
+import { trackChange } from '@langdiff/langdiff';
+
+interface UI {
+ items: string[];
+}
+
+// Track changes to any object
+const [obj, diffBuf] = trackChange({ items: [] });
+
+// Make modifications
+obj.items.push("new item");
+
+// Get JSON Patch operations
+const changes = diffBuf.flush();
+```
diff --git a/ts/docs/api/parser.md b/ts/docs/api/parser.md
new file mode 100644
index 0000000..9bd0af1
--- /dev/null
+++ b/ts/docs/api/parser.md
@@ -0,0 +1,438 @@
+# Parser Module
+
+The parser module provides streaming-aware data types and parsing capabilities for processing structured LLM outputs in real-time using a modern functional schema builder API.
+
+## Schema Builder Functions
+
+### string()
+
+Creates a streaming string schema that processes string values incrementally.
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+
+const schema = ld.string();
+const instance = schema.create();
+```
+
+**Methods:**
+- `create()` - Creates a `StreamingString` instance
+- `toZod()` - Returns `z.string()`
+- `describe(description)` - Add description metadata
+- `default(value)` - Set default value
+
+### array(itemSchema)
+
+Creates a streaming array schema that processes array items incrementally.
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+
+// Array of strings
+const stringArray = ld.array(ld.string());
+
+// Array of objects
+const objectArray = ld.array(ld.object({
+ name: ld.string(),
+ age: ld.number()
+}));
+
+// Array of atoms
+const numberArray = ld.array(ld.number());
+```
+
+**Methods:**
+- `create()` - Creates a `StreamingList` or `StreamingAtomList` instance
+- `toZod()` - Returns `z.array(itemSchema.toZod())`
+
+### object(fields)
+
+Creates a streaming object schema with specified fields.
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+
+const UserSchema = ld.object({
+ name: ld.string(),
+ age: ld.number(),
+ hobbies: ld.array(ld.string()),
+ profile: ld.object({
+ bio: ld.string(),
+ avatar: ld.string()
+ })
+});
+```
+
+**Methods:**
+- `create()` - Creates a `StreamingObject` instance with typed field access
+- `toZod()` - Returns `z.object()` with field schemas
+
+### number()
+
+Creates a number atom schema for non-streaming numeric values.
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+
+const numberSchema = ld.number();
+const instance = numberSchema.create(); // Returns Atom
+```
+
+### boolean()
+
+Creates a boolean atom schema for non-streaming boolean values.
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+
+const booleanSchema = ld.boolean();
+const instance = booleanSchema.create(); // Returns Atom
+```
+
+### atom(zodSchema)
+
+Creates a custom atom schema using any Zod schema for non-streaming values.
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+import { z } from 'zod';
+
+const dateSchema = ld.atom(z.date());
+const enumSchema = ld.atom(z.enum(['red', 'green', 'blue']));
+```
+
+### fromZod(zodSchema)
+
+Creates a streaming schema from an existing Zod schema, automatically converting Zod types to their streaming equivalents.
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+import { z } from 'zod';
+
+// Existing Zod schema
+const UserZodSchema = z.object({
+ name: z.string(),
+ age: z.number(),
+ hobbies: z.array(z.string())
+});
+
+// Convert to streaming schema
+const UserStreamingSchema = ld.fromZod(UserZodSchema);
+const user = UserStreamingSchema.create();
+
+// Use normally with streaming callbacks
+user.name.onAppend((chunk) => console.log(chunk));
+user.hobbies.onAppend((hobby, index) => {
+ hobby.onComplete((value) => console.log(`Hobby ${index}: ${value}`));
+});
+```
+
+## Core Streaming Classes
+
+### StreamingString
+
+Represents a string value that is built incrementally from chunks.
+
+**Key Methods:**
+- `onStart(callback)` - Called when streaming begins
+- `onAppend(callback)` - Called with each new chunk: `(chunk: string) => void`
+- `onComplete(callback)` - Called when complete: `(value: string | null) => void`
+
+### StreamingList
+
+Represents an array that receives items incrementally.
+
+**Key Methods:**
+- `onStart(callback)` - Called when streaming begins
+- `onAppend(callback)` - Called when new item added: `(item: T, index: number) => void`
+- `onComplete(callback)` - Called when complete: `(value: T[]) => void`
+
+### StreamingObject
+
+Represents an object with streaming field values.
+
+**Key Methods:**
+- `onStart(callback)` - Called when streaming begins
+- `onUpdate(callback)` - Called on any field update: `(value: Record) => void`
+- `onComplete(callback)` - Called when complete: `(value: T) => void`
+
+### Atom
+
+Represents atomic values (numbers, booleans, etc.) that are received as complete values.
+
+**Key Methods:**
+- `onStart(callback)` - Called when value is received
+- `onComplete(callback)` - Called with the final value: `(value: T) => void`
+
+## Parser
+
+Processes streaming JSON tokens and updates streaming objects incrementally.
+
+**Constructor:**
+```typescript
+import * as ld from '@langdiff/langdiff';
+
+const schema = ld.object({ title: ld.string() });
+const instance = schema.create();
+const parser = new ld.Parser(instance);
+```
+
+**Key Methods:**
+- `push(chunk: string)` - Process a chunk of JSON string
+- `complete()` - Mark parsing as complete and trigger final callbacks
+
+## Usage Examples
+
+### Basic Streaming
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+
+// Define schema
+const ShoppingList = ld.object({
+ items: ld.array(ld.string()),
+ completed: ld.boolean()
+});
+
+// Create instance and set up callbacks
+const list = ShoppingList.create();
+
+list.items.onAppend((item: ld.StreamingString, index: number) => {
+ console.log(`New item #${index} started`);
+
+ item.onAppend((chunk: string) => {
+ console.log(` Chunk: "${chunk}"`);
+ });
+
+ item.onComplete((finalValue: string | null) => {
+ console.log(` Completed: "${finalValue}"`);
+ });
+});
+
+// Parse streaming data
+const parser = new ld.Parser(list);
+const chunks = ['{"items": ["Mi', 'lk", "Br', 'ead"], "completed": true}'];
+
+for (const chunk of chunks) {
+ parser.push(chunk);
+}
+parser.complete();
+```
+
+### Nested Objects
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+
+const BlogPost = ld.object({
+ title: ld.string(),
+ author: ld.object({
+ name: ld.string(),
+ email: ld.string()
+ }),
+ comments: ld.array(ld.object({
+ text: ld.string(),
+ likes: ld.number()
+ }))
+});
+
+const post = BlogPost.create();
+
+// Set up nested callbacks
+post.author.name.onAppend((chunk: string) => {
+ console.log(`Author name: ${chunk}`);
+});
+
+post.comments.onAppend((comment, index: number) => {
+ comment.text.onAppend((chunk: string) => {
+ console.log(`Comment ${index}: ${chunk}`);
+ });
+
+ comment.likes.onComplete((likes: number) => {
+ console.log(`Comment ${index} has ${likes} likes`);
+ });
+});
+
+const parser = new ld.Parser(post);
+// ... process streaming JSON
+```
+
+### OpenAI Integration
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+import OpenAI from 'openai';
+import { zodResponseFormat } from 'openai/helpers/zod';
+
+const ArticleSchema = ld.object({
+ title: ld.string(),
+ sections: ld.array(ld.object({
+ heading: ld.string(),
+ content: ld.string()
+ }))
+});
+
+const client = new OpenAI();
+
+// Use Zod schema with OpenAI
+const stream = await client.chat.completions.create({
+ model: "gpt-4",
+ messages: [{role: "user", content: "Write an article about TypeScript"}],
+ stream: true,
+ response_format: zodResponseFormat(ArticleSchema.toZod(), 'Article'),
+});
+
+// Set up streaming response handling
+const article = ArticleSchema.create();
+const parser = new ld.Parser(article);
+
+article.sections.onAppend((section, index) => {
+ section.heading.onComplete((heading) => {
+ console.log(`Section ${index}: ${heading}`);
+ });
+
+ section.content.onAppend((chunk) => {
+ process.stdout.write(chunk); // Stream content in real-time
+ });
+});
+
+// Process stream
+for await (const chunk of stream) {
+ const content = chunk.choices[0]?.delta?.content;
+ if (content) {
+ parser.push(content);
+ }
+}
+parser.complete();
+```
+
+### Working with Atoms
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+import { z } from 'zod';
+
+const UserProfile = ld.object({
+ name: ld.string(),
+ age: ld.number(),
+ isActive: ld.boolean(),
+ role: ld.atom(z.enum(['admin', 'user', 'guest'])),
+ metadata: ld.atom(z.record(z.any()))
+});
+
+const profile = UserProfile.create();
+
+// Atoms receive complete values
+profile.age.onComplete((age: number) => {
+ console.log(`User is ${age} years old`);
+});
+
+profile.role.onComplete((role: 'admin' | 'user' | 'guest') => {
+ console.log(`User role: ${role}`);
+});
+```
+
+### Schema Customization
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+import { z } from 'zod';
+
+const ProductSchema = ld.object({
+ name: ld.string()
+ .describe("Product name")
+ .default("Unnamed Product"),
+ price: ld.number()
+ .describe("Price in USD"),
+ tags: ld.array(ld.string())
+ .describe("Product tags"),
+ category: ld.atom(z.string().min(1))
+ .describe("Product category")
+});
+
+// Get Zod schema for validation
+const zodSchema = ProductSchema.toZod();
+console.log(zodSchema._def); // Contains descriptions and defaults
+```
+
+### fromZod Integration
+
+Create streaming schemas from existing Zod schemas:
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+import { z } from 'zod';
+
+// Existing Zod schema
+const UserZodSchema = z.object({
+ name: z.string(),
+ age: z.number(),
+ hobbies: z.array(z.string())
+});
+
+// Convert to streaming schema
+const UserStreamingSchema = ld.fromZod(UserZodSchema);
+const user = UserStreamingSchema.create();
+
+// Use normally with streaming callbacks
+user.name.onAppend((chunk) => console.log(chunk));
+user.hobbies.onAppend((hobby, index) => {
+ hobby.onComplete((value) => console.log(`Hobby ${index}: ${value}`));
+});
+```
+
+## Type Aliases
+
+For convenience, the following type aliases are available:
+
+```typescript
+import { String, Object } from '@langdiff/langdiff';
+
+// String is an alias for StreamingString
+// Object is an alias for StreamingObject
+```
+
+**Note:** These are type aliases, not runtime values. Use them for type annotations:
+
+```typescript
+// ā
Correct usage - type annotations
+function handleString(str: String): void {
+ str.onAppend(chunk => console.log(chunk));
+}
+
+const stringInstance: String = ld.string().create();
+
+// ā Incorrect usage - runtime values
+const instance = new String(); // Error! String is not a class
+const result = String.create(); // Error! String is not a function
+```
+
+## Event System
+
+All streaming types support a consistent event system:
+
+### Event Order
+1. `onStart()` - Fired when streaming begins
+2. `onAppend()` - Fired for each chunk/item (StreamingString/StreamingList only)
+3. `onComplete()` - Fired when streaming finishes
+
+### Error Handling
+Events allow errors to propagate naturally - if a callback throws an error, it will bubble up to your application code:
+
+```typescript
+const schema = ld.object({ title: ld.string() });
+const instance = schema.create();
+
+instance.title.onAppend((chunk) => {
+ if (chunk.includes('bad_word')) {
+ throw new Error('Content filter violation');
+ }
+});
+
+try {
+ const parser = new ld.Parser(instance);
+ parser.push('{"title": "bad_word detected"}');
+} catch (error) {
+ console.error('Streaming error:', error.message);
+}
+```
\ No newline at end of file
diff --git a/ts/docs/api/tracker.md b/ts/docs/api/tracker.md
new file mode 100644
index 0000000..f6a7e4a
--- /dev/null
+++ b/ts/docs/api/tracker.md
@@ -0,0 +1,275 @@
+# Tracker Module
+
+The tracker module provides automatic change tracking for TypeScript objects, generating JSON Patch operations for efficient state synchronization.
+
+## Core Functions
+
+### trackChange()
+
+Wrap objects for automatic change tracking.
+
+**Signature:**
+```typescript
+function trackChange(
+ obj: T,
+ trackerCls?: new () => ChangeTracker
+): [T, DiffBuffer]
+```
+
+**Parameters:**
+- `obj` - The object to track changes for
+- `trackerCls` - Optional tracker class (defaults to EfficientJSONPatchChangeTracker)
+
+**Returns:** A tuple containing the proxied object and a diff buffer.
+
+### applyChange()
+
+Apply JSON Patch operations to an object.
+
+**Signature:**
+```typescript
+function applyChange(obj: any, operations: Operation[]): void
+```
+
+**Parameters:**
+- `obj` - The target object to apply changes to
+- `operations` - Array of JSON Patch operations to apply
+
+## Change Trackers
+
+### ChangeTracker
+
+Abstract base class for change tracking implementations.
+
+**Key Methods:**
+- `track(obj: T): T` - Track changes to an object and return a proxy
+- `flush(): Operation[]` - Get and clear accumulated changes
+- `getChanges(): Operation[]` - Get accumulated changes without clearing
+- `clear(): void` - Clear accumulated changes
+
+### JSONPatchChangeTracker
+
+JSON Patch change tracker that generates standard JSON Patch operations (RFC 6902 compliant).
+
+**Features:**
+- Standard `add`, `remove`, `replace` operations
+- Custom `append` operation for efficient string concatenation
+- Full JSON Patch compliance
+
+### EfficientJSONPatchChangeTracker
+
+Enhanced JSON Patch change tracker with optimized string append operations.
+
+**Features:**
+- All JSONPatchChangeTracker features
+- Optimized string append detection
+- Better performance for streaming text scenarios
+
+## Interfaces
+
+### Operation
+
+Represents a JSON Patch operation.
+
+**Interface:**
+```typescript
+interface Operation {
+ op: string; // Operation type: 'add', 'remove', 'replace', 'append', etc.
+ path: string; // JSON Pointer path
+ value?: any; // Value for add/replace/append operations
+ from?: string; // Source path for move/copy operations
+}
+```
+
+### DiffBuffer
+
+Interface for objects that provide change tracking capabilities.
+
+**Interface:**
+```typescript
+interface DiffBuffer {
+ flush(): Operation[]; // Get and clear changes
+ getChanges(): Operation[]; // Get changes without clearing
+ clear(): void; // Clear accumulated changes
+}
+```
+
+## Usage Examples
+
+### Basic Change Tracking
+
+```typescript
+import { trackChange } from '@langdiff/langdiff';
+
+interface UserProfile {
+ name: string;
+ age: number;
+ hobbies: string[];
+}
+
+// Wrap object for tracking
+const [profile, diffBuf] = trackChange({
+ name: "",
+ age: 0,
+ hobbies: []
+});
+
+// Make changes
+profile.name = "Alice";
+profile.age = 25;
+profile.hobbies.push("reading");
+
+// Get accumulated changes
+const changes = diffBuf.flush();
+console.log(changes);
+// [
+// {"op": "replace", "path": "/name", "value": "Alice"},
+// {"op": "replace", "path": "/age", "value": 25},
+// {"op": "add", "path": "/hobbies/-", "value": "reading"}
+// ]
+```
+
+### Different Tracker Types
+
+```typescript
+import { trackChange, JSONPatchChangeTracker, EfficientJSONPatchChangeTracker } from '@langdiff/langdiff';
+
+// Standard JSON Patch (RFC 6902 compliant)
+const [profile1, diffBuf1] = trackChange(
+ { name: "", age: 0, hobbies: [] },
+ JSONPatchChangeTracker
+);
+
+// Efficient tracker with append operations (default)
+const [profile2, diffBuf2] = trackChange(
+ { name: "", age: 0, hobbies: [] },
+ EfficientJSONPatchChangeTracker // This is the default
+);
+```
+
+### String Append Optimization
+
+```typescript
+interface ChatMessage {
+ content: string;
+ timestamp: number;
+}
+
+const [message, diffBuf] = trackChange({
+ content: "",
+ timestamp: Date.now()
+});
+
+// Simulate streaming text - efficient append operations are detected
+message.content = "Hello";
+message.content = "Hello world";
+message.content = "Hello world! How are you?";
+
+const changes = diffBuf.flush();
+// [
+// {"op": "replace", "path": "/content", "value": "Hello"},
+// {"op": "append", "path": "/content", "value": " world"},
+// {"op": "append", "path": "/content", "value": "! How are you?"}
+// ]
+```
+
+### Applying Changes
+
+```typescript
+import { applyChange } from '@langdiff/langdiff';
+
+// Original object
+const original = { count: 0, items: [] as string[] };
+
+// Changes to apply
+const changes = [
+ { op: "replace", path: "/count", value: 5 },
+ { op: "add", path: "/items/-", value: "new item" },
+ { op: "append", path: "/items/0", value: " (updated)" }
+];
+
+// Apply changes
+applyChange(original, changes);
+console.log(original);
+// { count: 5, items: ["new item (updated)"] }
+```
+
+### Integration with Streaming Parser
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+
+// Define schema using modern API
+const Response = ld.object({
+ content: ld.string()
+});
+
+// Create tracked UI state
+interface UIState {
+ displayText: string;
+ isComplete: boolean;
+}
+
+const [uiState, diffBuf] = trackChange({
+ displayText: "",
+ isComplete: false
+});
+
+// Set up streaming response
+const response = Response.create();
+response.content.onAppend((chunk: string) => {
+ uiState.displayText += chunk; // Tracked as append operations
+});
+
+response.onComplete(() => {
+ uiState.isComplete = true;
+});
+
+// Parse streaming JSON
+const parser = new ld.Parser(response);
+parser.push('{"content": "Hello');
+parser.push(' world!"}');
+parser.complete();
+
+// Get efficient diff operations for frontend
+const uiChanges = diffBuf.flush();
+console.log(uiChanges);
+// Efficient append operations instead of full content replacement
+```
+
+## JSON Patch Operations
+
+The tracker module supports standard JSON Patch operations plus a custom `append` operation:
+
+### Standard Operations
+
+- **`add`** - Add a new value at the specified path
+- **`remove`** - Remove the value at the specified path
+- **`replace`** - Replace the value at the specified path
+- **`move`** - Move a value from one path to another
+- **`copy`** - Copy a value from one path to another
+- **`test`** - Test that the value at the path matches the given value
+
+### Custom Operations
+
+- **`append`** - Efficiently append to a string value (non-standard but useful for streaming)
+
+### Path Format
+
+JSON Patch uses JSON Pointer (RFC 6901) for paths:
+
+- `/name` - Root level property "name"
+- `/items/0` - First item in "items" array
+- `/items/-` - Append to "items" array
+- `/user/profile/email` - Nested property access
+
+### Examples
+
+```typescript
+const operations = [
+ { op: "add", path: "/items/-", value: "new item" },
+ { op: "replace", path: "/status", value: "active" },
+ { op: "remove", path: "/temp" },
+ { op: "append", path: "/message", value: " (updated)" }
+];
+```
diff --git a/ts/docs/diagram.png b/ts/docs/diagram.png
new file mode 100644
index 0000000..b27aafd
Binary files /dev/null and b/ts/docs/diagram.png differ
diff --git a/ts/docs/index.md b/ts/docs/index.md
new file mode 100644
index 0000000..9a5fe38
--- /dev/null
+++ b/ts/docs/index.md
@@ -0,0 +1,105 @@
+# LangDiff
+
+LangDiff is a TypeScript library that solves the hard problems of streaming structured LLM outputs to frontends.
+
+
+
+LangDiff provides intelligent partial parsing with granular, type-safe events as JSON structures build token by token, plus automatic JSON Patch generation for efficient frontend synchronization. Build responsive AI applications where your backend structures and frontend experiences can evolve independently.
+
+## Core Features
+
+### Streaming Parsing
+- Define schemas for streaming structured outputs using class-based models
+- Receive granular, type-safe callbacks (`onAppend`, `onUpdate`, `onComplete`) as tokens stream in
+- Convert to Zod schemas for seamless interop with existing libraries and SDKs like OpenAI SDK
+
+### Change Tracking
+- Track mutations without changing your code patterns by instrumenting existing objects and arrays
+- Generate JSON Patch diffs automatically for efficient state synchronization between frontend and backend
+
+```typescript
+response.text.onAppend((chunk: string) => {
+ ui.body[ui.body.length - 1] = ui.body[ui.body.length - 1].slice(5, -6); // remove tags
+ ui.body.push(`${chunk}`);
+});
+
+// Tracked UI changes:
+// {"op": "add", "path": "/body/-", "value": "Hell"}
+// {"op": "replace", "path": "/body/0", "value": "Hell"}
+// {"op": "add", "path": "/body/-", "value": "o, world!"}
+```
+
+## Installation
+
+```bash
+npm install langdiff
+```
+
+For yarn:
+
+```bash
+yarn add langdiff
+```
+
+## Quick Example
+
+```typescript
+import * as ld from '@langdiff/langdiff';
+import OpenAI from 'openai';
+
+class ArticleResponse extends ld.Object {
+ title!: ld.String;
+ sections!: ld.List;
+
+ protected _initializeFields(): void {
+ this.addField('title', new ld.String());
+ this.addField('sections', new ld.List(ld.String));
+ }
+}
+
+// Set up streaming callbacks
+const response = new ArticleResponse();
+
+response.title.onAppend((chunk: string) => {
+ process.stdout.write(`Title: ${chunk}`);
+});
+
+response.sections.onAppend((section: ld.String, index: number) => {
+ console.log(`\n\nSection ${index + 1}:`);
+
+ section.onAppend((chunk: string) => {
+ process.stdout.write(chunk);
+ });
+});
+
+// Stream from OpenAI
+const client = new OpenAI();
+const stream = await client.chat.completions.create({
+ model: "gpt-4o-mini",
+ messages: [{ role: "user", content: "Write a short article about TypeScript" }],
+ response_format: { type: "json_object" },
+ stream: true
+});
+
+const parser = new ld.Parser(response);
+
+for await (const chunk of stream) {
+ const content = chunk.choices[0]?.delta?.content;
+ if (content) {
+ parser.push(content);
+ }
+}
+
+parser.complete();
+```
+
+## Why LangDiff?
+
+Modern AI applications increasingly rely on LLMs to generate structured data rather than just conversational text. While LLM providers offer structured output capabilities, streaming these outputs poses unique challenges:
+
+- **Partial JSON Parsing**: Standard parsers can't handle incomplete tokens like `{"sentence": "Hello,` until closing quotes arrive
+- **Type Safety**: Lose static type checking when dealing with partial objects
+- **Frontend Coupling**: Tightly coupling UI to LLM schemas creates maintenance issues
+- **Inefficient Updates**: Sending entire objects instead of just changes wastes bandwidth
+
+LangDiff solves these problems through intelligent streaming parsing and change-based synchronization, enabling you to build responsive, maintainable AI applications.
\ No newline at end of file
diff --git a/ts/examples/01-basic-streaming.ts b/ts/examples/01-basic-streaming.ts
new file mode 100644
index 0000000..db48c81
--- /dev/null
+++ b/ts/examples/01-basic-streaming.ts
@@ -0,0 +1,91 @@
+/**
+ * Basic Streaming Parser Example
+ *
+ * This example demonstrates how to use LangDiff's streaming parser
+ * to handle progressive JSON parsing with type-safe callbacks.
+ */
+
+import * as ld from '../src';
+
+// Define a simple data structure for a shopping list
+const ShoppingList = ld.object({
+ items: ld.array(ld.string())
+});
+
+interface UIState {
+ items: string[];
+ itemsComplete: boolean[];
+}
+
+async function basicStreamingExample() {
+ console.log('š Basic Streaming Parser Example\n');
+
+ // Create the streaming object
+ const shoppingList = ShoppingList.create();
+
+ // Create UI state
+ const ui: UIState = {
+ items: [],
+ itemsComplete: []
+ };
+
+ // Set up event handlers
+ shoppingList.items.onAppend((item: ld.StreamingString, index: number) => {
+ console.log(`š New item started: #${index}`);
+ ui.items[index] = '';
+ ui.itemsComplete[index] = false;
+
+ item.onAppend((chunk: string) => {
+ ui.items[index] += chunk;
+ console.log(` ā³ "${ui.items[index]}"`);
+ });
+
+ item.onComplete(() => {
+ ui.itemsComplete[index] = true;
+ console.log(` ā
Item #${index} completed: "${ui.items[index]}"`);
+ });
+ });
+
+ shoppingList.items.onComplete(() => {
+ console.log('\nš All items completed!\n');
+ });
+
+ // Simulate streaming JSON data
+ const mockStreamData = [
+ '{"items": ["',
+ 'Milk',
+ '", "',
+ 'Bread',
+ '", "',
+ 'Apple',
+ 's", "',
+ 'Banan',
+ 'as"]}',
+ ];
+
+ const parser = new ld.Parser(shoppingList);
+
+ console.log('Starting to parse streaming data...\n');
+
+ for (const chunk of mockStreamData) {
+ console.log(`š Processing chunk: "${chunk}"`);
+ parser.push(chunk);
+ console.log(`Current state: ${JSON.stringify(ui.items)}\n`);
+
+ // Simulate network delay
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+
+ parser.complete();
+
+ console.log('Final UI State:');
+ console.log(`Items: ${JSON.stringify(ui.items)}`);
+ console.log(`Completed: ${JSON.stringify(ui.itemsComplete)}`);
+}
+
+// Run the example if this file is executed directly
+if (require.main === module) {
+ basicStreamingExample().catch(console.error);
+}
+
+export { basicStreamingExample };
\ No newline at end of file
diff --git a/ts/examples/02-change-tracking.ts b/ts/examples/02-change-tracking.ts
new file mode 100644
index 0000000..d31b822
--- /dev/null
+++ b/ts/examples/02-change-tracking.ts
@@ -0,0 +1,164 @@
+/**
+ * Change Tracking Example
+ *
+ * This example demonstrates how to use LangDiff's change tracking
+ * to monitor object mutations and generate JSON Patch operations.
+ */
+
+import {
+ trackChange,
+ applyChange,
+ Operation,
+} from '../src';
+
+interface TodoItem {
+ id: number;
+ text: string;
+ completed: boolean;
+ priority: 'low' | 'medium' | 'high';
+}
+
+interface TodoApp {
+ todos: TodoItem[];
+ filter: 'all' | 'active' | 'completed';
+ stats: {
+ total: number;
+ completed: number;
+ active: number;
+ };
+}
+
+async function changeTrackingExample() {
+ console.log('š Change Tracking Example\n');
+
+ // Initial state
+ const initialState: TodoApp = {
+ todos: [
+ { id: 1, text: 'Learn LangDiff', completed: false, priority: 'high' },
+ { id: 2, text: 'Build awesome app', completed: false, priority: 'medium' }
+ ],
+ filter: 'all',
+ stats: { total: 2, completed: 0, active: 2 }
+ };
+
+ // Track changes to the state
+ const [state, diffBuffer] = trackChange(initialState);
+
+ console.log('Initial state:');
+ console.log(JSON.stringify(state, null, 2));
+ console.log('\n--- Making Changes ---\n');
+
+ // Simulate a series of user actions
+ console.log('1. Add a new todo item');
+ state.todos.push({
+ id: 3,
+ text: 'Write documentation',
+ completed: false,
+ priority: 'low'
+ });
+
+ let changes = diffBuffer.flush();
+ console.log('Changes:', JSON.stringify(changes, null, 2));
+ console.log();
+
+ console.log('2. Mark first todo as completed');
+ state.todos[0].completed = true;
+
+ changes = diffBuffer.flush();
+ console.log('Changes:', JSON.stringify(changes, null, 2));
+ console.log();
+
+ console.log('3. Update stats');
+ state.stats.completed = 1;
+ state.stats.active = 2;
+
+ changes = diffBuffer.flush();
+ console.log('Changes:', JSON.stringify(changes, null, 2));
+ console.log();
+
+ console.log('4. Change filter');
+ state.filter = 'active';
+
+ changes = diffBuffer.flush();
+ console.log('Changes:', JSON.stringify(changes, null, 2));
+ console.log();
+
+ console.log('5. Update todo text');
+ state.todos[1].text = 'Build amazing app with LangDiff';
+
+ changes = diffBuffer.flush();
+ console.log('Changes:', JSON.stringify(changes, null, 2));
+ console.log();
+
+ console.log('Final state:');
+ console.log(JSON.stringify(state, null, 2));
+ console.log();
+
+ // Demonstrate applying changes to another object
+ console.log('--- Applying Changes to Remote Object ---\n');
+
+ const remoteState: TodoApp = {
+ todos: [
+ { id: 1, text: 'Learn LangDiff', completed: false, priority: 'high' },
+ { id: 2, text: 'Build awesome app', completed: false, priority: 'medium' }
+ ],
+ filter: 'all',
+ stats: { total: 2, completed: 0, active: 2 }
+ };
+
+ console.log('Remote state before sync:');
+ console.log(JSON.stringify(remoteState, null, 2));
+
+ // Collect all the changes made
+ const allChanges: Operation[] = [
+ { op: 'add', path: '/todos/-', value: { id: 3, text: 'Write documentation', completed: false, priority: 'low' } },
+ { op: 'replace', path: '/todos/0/completed', value: true },
+ { op: 'replace', path: '/stats/completed', value: 1 },
+ { op: 'replace', path: '/stats/active', value: 2 },
+ { op: 'replace', path: '/filter', value: 'active' },
+ { op: 'replace', path: '/todos/1/text', value: 'Build amazing app with LangDiff' }
+ ];
+
+ // Apply changes
+ applyChange(remoteState, allChanges);
+
+ console.log('\nRemote state after sync:');
+ console.log(JSON.stringify(remoteState, null, 2));
+
+ console.log('\nā
States are now synchronized!');
+}
+
+// Helper function to demonstrate incremental changes
+async function incrementalChangeExample() {
+ console.log('\n--- Incremental Change Example ---\n');
+
+ interface Counter {
+ value: number;
+ history: number[];
+ }
+
+ const [counter, diffBuffer] = trackChange({
+ value: 0,
+ history: []
+ });
+
+ console.log('Incrementing counter and tracking changes:');
+
+ for (let i = 1; i <= 5; i++) {
+ counter.value = i;
+ counter.history.push(i);
+
+ const changes = diffBuffer.flush();
+ console.log(`Step ${i}:`, JSON.stringify(changes, null, 2));
+ }
+}
+
+// Run the examples if this file is executed directly
+if (require.main === module) {
+ Promise.resolve()
+ .then(() => changeTrackingExample())
+ .then(() => incrementalChangeExample())
+ .catch(console.error);
+}
+
+export { changeTrackingExample, incrementalChangeExample };
\ No newline at end of file
diff --git a/ts/examples/03-openai-integration.ts b/ts/examples/03-openai-integration.ts
new file mode 100644
index 0000000..ecb65bc
--- /dev/null
+++ b/ts/examples/03-openai-integration.ts
@@ -0,0 +1,335 @@
+/**
+ * OpenAI Integration Example
+ *
+ * This example demonstrates how to integrate LangDiff with OpenAI's
+ * streaming API to create real-time AI applications.
+ */
+
+import OpenAI from 'openai';
+import { zodResponseFormat } from 'openai/helpers/zod';
+import {
+ StreamingString,
+ Parser,
+ trackChange,
+ Operation,
+} from '../src';
+import * as ld from '../src';
+
+// Generate StreamingObject class using ld namespace (better type inference)
+const CodeReviewResponse = ld.object({
+ summary: ld.string(),
+ issues: ld.array(ld.string()),
+ suggestions: ld.array(ld.string()),
+ score: ld.string()
+});
+
+// Derive the Zod schema for OpenAI SDK
+const CodeReviewSchema = CodeReviewResponse.toZod();
+
+interface CodeReview {
+ summary: string;
+ issues: string[];
+ suggestions: string[];
+ score: string;
+ status: {
+ summaryComplete: boolean;
+ issuesComplete: boolean;
+ suggestionsComplete: boolean;
+ scoreComplete: boolean;
+ };
+}
+
+async function openaiStreamingExample() {
+ console.log('š¤ OpenAI Integration Example\n');
+
+ // Initialize OpenAI client (you need to set OPENAI_API_KEY environment variable)
+ const openai = new OpenAI({
+ apiKey: process.env.OPENAI_API_KEY || 'your-api-key-here',
+ baseURL: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1',
+ });
+
+ // Create the streaming response structure
+ const response = CodeReviewResponse.create();
+
+ // Initialize UI state
+ const initialReview: CodeReview = {
+ summary: '',
+ issues: [],
+ suggestions: [],
+ score: '',
+ status: {
+ summaryComplete: false,
+ issuesComplete: false,
+ suggestionsComplete: false,
+ scoreComplete: false,
+ }
+ };
+
+ // Track changes for efficient updates
+ const [review, diffBuffer] = trackChange(initialReview);
+
+ // Set up event handlers
+ setupEventHandlers(response, review);
+
+ // Sample code to review
+ const codeToReview = `
+function calculateTotal(items) {
+ var total = 0;
+ for (var i = 0; i < items.length; i++) {
+ total += items[i].price * items[i].quantity;
+ }
+ return total;
+}
+`;
+
+ const systemPrompt = `You are a code reviewer. Analyze the provided code and respond with a JSON object containing:
+- summary: A brief overview of the code
+- issues: An array of potential problems or bugs
+- suggestions: An array of improvement recommendations
+- score: A score from 1-10 with brief explanation
+
+Be concise and practical. Focus on real issues and actionable suggestions.`;
+
+ console.log('š Starting OpenAI streaming...\n');
+
+ try {
+ // This is a mock example since we don't want to require a real API key
+ if (process.env.OPENAI_API_KEY && process.env.OPENAI_API_KEY !== 'your-api-key-here') {
+ await streamWithRealAPI(openai, systemPrompt, codeToReview, response, review, diffBuffer);
+ } else {
+ await streamWithMockData(response, review, diffBuffer);
+ }
+
+ } catch (error) {
+ console.error('ā Error:', error);
+ }
+}
+
+async function streamWithRealAPI(
+ openai: OpenAI,
+ systemPrompt: string,
+ codeToReview: string,
+ response: ld.infer,
+ review: CodeReview,
+ diffBuffer: any
+) {
+ const stream = await openai.chat.completions.create({
+ model: 'gpt-5',
+ messages: [
+ { role: 'system', content: systemPrompt },
+ { role: 'user', content: `Please review this code:\n\n${codeToReview}` }
+ ],
+ stream: true,
+ response_format: zodResponseFormat(CodeReviewSchema, 'code_review'),
+ });
+
+ const parser = new Parser(response);
+ const logger = new ChangeLogger();
+
+ for await (const chunk of stream) {
+ const content = chunk.choices[0]?.delta?.content;
+ if (content) {
+ parser.push(content);
+
+ // Get incremental changes
+ const changes = diffBuffer.flush();
+ if (changes.length > 0) {
+ console.log('š” Streaming update:');
+ logger.log(changes);
+ console.log('Current state:', JSON.stringify(review, null, 2));
+ console.log('---\n');
+ }
+ }
+ }
+
+ parser.complete();
+ const finalChanges = diffBuffer.flush();
+ if (finalChanges.length > 0) {
+ console.log('š Final changes:');
+ logger.log(finalChanges);
+ }
+}
+
+async function streamWithMockData(
+ response: ld.infer,
+ review: CodeReview,
+ diffBuffer: any
+) {
+ console.log('š Using mock data (set OPENAI_API_KEY for real API)');
+
+ const mockStreamData = [
+ '{"summary": "',
+ 'The function calculates',
+ ' total cost for items',
+ ' but uses older',
+ ' JavaScript syntax',
+ '", "issues": ["',
+ 'Uses var instead',
+ ' of const/let',
+ '", "',
+ 'No input validation',
+ '", "',
+ 'No error handling',
+ ' for missing properties',
+ '"], "suggestions": ["',
+ 'Use const/let',
+ ' for variable declarations',
+ '", "',
+ 'Add input validation',
+ '", "',
+ 'Use array methods',
+ ' like reduce()',
+ '", "',
+ 'Add JSDoc comments',
+ '"], "score": "',
+ '6/10 - Functional',
+ ' but needs modernization',
+ ' and error handling',
+ '"}',
+ ];
+
+ const parser = new Parser(response);
+ const logger = new ChangeLogger();
+
+ for (const chunk of mockStreamData) {
+ console.log(`š Processing: "${chunk}"`);
+ parser.push(chunk);
+
+ const changes = diffBuffer.flush();
+ if (changes.length > 0) {
+ console.log('š” Streaming update:');
+ logger.log(changes);
+ console.log('Current state:', JSON.stringify(review, null, 2));
+ console.log('---\n');
+ }
+
+ // Simulate network delay
+ await new Promise(resolve => setTimeout(resolve, 300));
+ }
+
+ parser.complete();
+ const finalChanges = diffBuffer.flush();
+ if (finalChanges.length > 0) {
+ console.log('š Final changes:');
+ logger.log(finalChanges);
+ }
+
+ console.log('\nā
Final review:');
+ console.log(JSON.stringify(review, null, 2));
+}
+
+function setupEventHandlers(response: ld.infer, review: CodeReview) {
+ // Summary handlers
+ response.summary.onAppend((chunk) => {
+ review.summary += chunk;
+ });
+ response.summary.onComplete(() => {
+ review.status.summaryComplete = true;
+ console.log('š Summary completed');
+ });
+
+ // Issues handlers
+ response.issues.onAppend((issue: StreamingString, index: number) => {
+ review.issues[index] = '';
+ issue.onAppend((chunk: string) => {
+ review.issues[index] += chunk;
+ });
+ issue.onComplete(() => {
+ console.log(`š Issue #${index + 1} completed: "${review.issues[index]}"`);
+ });
+ });
+ response.issues.onComplete(() => {
+ review.status.issuesComplete = true;
+ console.log('š All issues completed');
+ });
+
+ // Suggestions handlers
+ response.suggestions.onAppend((suggestion: StreamingString, index: number) => {
+ review.suggestions[index] = '';
+ suggestion.onAppend((chunk: string) => {
+ review.suggestions[index] += chunk;
+ });
+ suggestion.onComplete(() => {
+ console.log(`š” Suggestion #${index + 1} completed: "${review.suggestions[index]}"`);
+ });
+ });
+ response.suggestions.onComplete(() => {
+ review.status.suggestionsComplete = true;
+ console.log('š” All suggestions completed');
+ });
+
+ // Score handlers
+ response.score.onAppend((chunk: string) => {
+ review.score += chunk;
+ });
+ response.score.onComplete(() => {
+ review.status.scoreComplete = true;
+ console.log(`ā Score completed: "${review.score}"`);
+ });
+}
+
+class ChangeLogger {
+ private count = 0;
+
+ log(changes: Operation[]) {
+ changes.forEach((change, i) => {
+ console.log(` ${this.count + i + 1}. ${change.op} ${change.path}: ${JSON.stringify(change.value)}`);
+ });
+ this.count += changes.length;
+ }
+}
+
+// Example of using LangDiff in a web server context
+async function webServerExample() {
+ console.log('\nš Web Server Example (Express-like)\n');
+
+ // Simulate server-sent events
+ function sendSSE(data: any) {
+ console.log(`data: ${JSON.stringify(data)}\n`);
+ }
+
+ const response = CodeReviewResponse.create();
+ const [review, diffBuffer] = trackChange({
+ summary: '',
+ issues: [],
+ suggestions: [],
+ score: '',
+ status: {
+ summaryComplete: false,
+ issuesComplete: false,
+ suggestionsComplete: false,
+ scoreComplete: false,
+ }
+ });
+
+ setupEventHandlers(response, review);
+
+ // Simulate processing
+ const mockData = ['{"summary": "Good code', '" , "issues": ["Minor issue"], "score": "8/10"}'];
+ const parser = new Parser(response);
+
+ for (const chunk of mockData) {
+ parser.push(chunk);
+ const changes = diffBuffer.flush();
+ if (changes.length > 0) {
+ // Send only the changes to clients
+ sendSSE({ type: 'update', changes });
+ }
+ }
+
+ parser.complete();
+ const finalChanges = diffBuffer.flush();
+ if (finalChanges.length > 0) {
+ sendSSE({ type: 'complete', changes: finalChanges });
+ }
+}
+
+// Run the example if this file is executed directly
+if (require.main === module) {
+ Promise.resolve()
+ .then(() => openaiStreamingExample())
+ .then(() => webServerExample())
+ .catch(console.error);
+}
+
+export { openaiStreamingExample, webServerExample };
\ No newline at end of file
diff --git a/ts/examples/04-frontend-integration.ts b/ts/examples/04-frontend-integration.ts
new file mode 100644
index 0000000..51b9159
--- /dev/null
+++ b/ts/examples/04-frontend-integration.ts
@@ -0,0 +1,336 @@
+/**
+ * Frontend Integration Example
+ *
+ * This example demonstrates how to integrate LangDiff with frontend frameworks
+ * using Server-Sent Events (SSE) and WebSocket patterns.
+ */
+
+import {
+ trackChange,
+ applyChange,
+ Operation,
+} from '../src';
+import * as ld from '../src';
+
+// Define a chat message structure
+const ChatMessage = ld.object({
+ role: ld.string(),
+ content: ld.string(),
+ timestamp: ld.number(),
+});
+
+// Define a chat conversation structure
+const ChatConversation = ld.object({
+ messages: ld.array(ChatMessage),
+ status: ld.string()
+});
+
+interface UIMessage {
+ role: string;
+ content: string;
+ timestamp: number;
+ isComplete: boolean;
+ isStreaming: boolean;
+}
+
+interface ChatUI {
+ messages: UIMessage[];
+ status: string;
+ isConnected: boolean;
+}
+
+// Simulate Server-Sent Events
+class MockSSEConnection {
+ private listeners: Map = new Map();
+
+ on(event: string, callback: Function) {
+ if (!this.listeners.has(event)) {
+ this.listeners.set(event, []);
+ }
+ this.listeners.get(event)!.push(callback);
+ }
+
+ emit(event: string, data: any) {
+ const callbacks = this.listeners.get(event) || [];
+ callbacks.forEach(callback => callback(data));
+ }
+
+ close() {
+ this.listeners.clear();
+ }
+}
+
+async function frontendIntegrationExample() {
+ console.log('š Frontend Integration Example\n');
+
+ // Initialize UI state
+ const initialState: ChatUI = {
+ messages: [],
+ status: 'connecting',
+ isConnected: false,
+ };
+
+ const [ui, diffBuffer] = trackChange(initialState);
+
+ // Simulate SSE connection
+ const sse = new MockSSEConnection();
+
+ // Set up SSE event handlers
+ sse.on('connected', () => {
+ console.log('ā
Connected to server');
+ ui.status = 'connected';
+ ui.isConnected = true;
+ logUIChanges(diffBuffer.flush());
+ });
+
+ sse.on('update', (data: { changes: Operation[] }) => {
+ console.log('šØ Received update from server');
+ applyChange(ui, data.changes);
+ console.log('Applied changes:', JSON.stringify(data.changes, null, 2));
+ });
+
+ sse.on('message_start', (data: { messageIndex: number }) => {
+ console.log(`š¬ New message started: #${data.messageIndex}`);
+ ui.messages.push({
+ role: '',
+ content: '',
+ timestamp: Date.now(),
+ isComplete: false,
+ isStreaming: true,
+ });
+ logUIChanges(diffBuffer.flush());
+ });
+
+ sse.on('chunk', (data: { changes: Operation[] }) => {
+ applyChange(ui, data.changes);
+ console.log('š Content chunk received');
+ logCurrentMessage(ui);
+ });
+
+ sse.on('message_complete', (data: { messageIndex: number }) => {
+ console.log(`ā
Message #${data.messageIndex} completed`);
+ if (ui.messages[data.messageIndex]) {
+ ui.messages[data.messageIndex].isComplete = true;
+ ui.messages[data.messageIndex].isStreaming = false;
+ }
+ logUIChanges(diffBuffer.flush());
+ });
+
+ sse.on('disconnected', () => {
+ console.log('ā Disconnected from server');
+ ui.status = 'disconnected';
+ ui.isConnected = false;
+ logUIChanges(diffBuffer.flush());
+ });
+
+ // Simulate server interactions
+ await simulateServerInteraction(sse);
+
+ console.log('\nš Final UI state:');
+ console.log(JSON.stringify(ui, null, 2));
+
+ // Cleanup
+ sse.close();
+}
+
+async function simulateServerInteraction(sse: MockSSEConnection) {
+ console.log('š Simulating server interaction...\n');
+
+ // Connect
+ sse.emit('connected', {});
+ await delay(500);
+
+ // Simulate streaming a user message
+ sse.emit('message_start', { messageIndex: 0 });
+ await delay(200);
+
+ const userMessageChanges = [
+ { op: 'replace', path: '/messages/0/role', value: 'user' },
+ { op: 'append', path: '/messages/0/content', value: 'Hello,' },
+ ];
+ sse.emit('chunk', { changes: userMessageChanges });
+ await delay(200);
+
+ const userMessageChanges2 = [
+ { op: 'append', path: '/messages/0/content', value: ' how can you help me?' },
+ ];
+ sse.emit('chunk', { changes: userMessageChanges2 });
+ await delay(200);
+
+ sse.emit('message_complete', { messageIndex: 0 });
+ await delay(500);
+
+ // Simulate streaming an assistant response
+ sse.emit('message_start', { messageIndex: 1 });
+ await delay(200);
+
+ const assistantMessageChanges = [
+ { op: 'replace', path: '/messages/1/role', value: 'assistant' },
+ { op: 'append', path: '/messages/1/content', value: 'I can help you with' },
+ ];
+ sse.emit('chunk', { changes: assistantMessageChanges });
+ await delay(300);
+
+ const assistantMessageChanges2 = [
+ { op: 'append', path: '/messages/1/content', value: ' many things! I can answer questions,' },
+ ];
+ sse.emit('chunk', { changes: assistantMessageChanges2 });
+ await delay(300);
+
+ const assistantMessageChanges3 = [
+ { op: 'append', path: '/messages/1/content', value: ' help with coding, and provide explanations.' },
+ ];
+ sse.emit('chunk', { changes: assistantMessageChanges3 });
+ await delay(200);
+
+ sse.emit('message_complete', { messageIndex: 1 });
+ await delay(500);
+}
+
+// React-like component example (pseudo-code)
+function ReactComponentExample() {
+ console.log('\nāļø React Integration Example (Pseudo-code)\n');
+
+ const reactExample = `
+import React, { useState, useEffect } from 'react';
+import { trackChange, applyChange } from '@langdiff/langdiff';
+
+function ChatComponent() {
+ const [chatState, setChatState] = useState({
+ messages: [],
+ status: 'disconnected',
+ });
+
+ useEffect(() => {
+ // Track changes for efficient updates
+ const [state, diffBuffer] = trackChange(chatState);
+
+ // Set up SSE connection
+ const eventSource = new EventSource('/api/chat/stream');
+
+ eventSource.onmessage = (event) => {
+ const data = JSON.parse(event.data);
+
+ if (data.changes) {
+ // Apply incremental changes instead of replacing entire state
+ applyChange(state, data.changes);
+ setChatState({ ...state });
+ }
+ };
+
+ return () => {
+ eventSource.close();
+ };
+ }, []);
+
+ return (
+
+
Status: {chatState.status}
+
+ {chatState.messages.map((message, index) => (
+
+
{message.role}
+
+ {message.content}
+ {message.isStreaming && |}
+
+
+ ))}
+
+
+ );
+}
+`;
+
+ console.log(reactExample);
+}
+
+// Vue-like component example (pseudo-code)
+function VueComponentExample() {
+ console.log('\nš Vue Integration Example (Pseudo-code)\n');
+
+ const vueExample = `
+
+
+
Status: {{ chatState.status }}
+
+
+
{{ message.role }}
+
+ {{ message.content }}
+ |
+
+
+
+
+
+
+
+`;
+
+ console.log(vueExample);
+}
+
+function logUIChanges(changes: Operation[]) {
+ if (changes.length > 0) {
+ console.log('š UI Changes:', JSON.stringify(changes, null, 2));
+ }
+}
+
+function logCurrentMessage(ui: ChatUI) {
+ const lastMessage = ui.messages[ui.messages.length - 1];
+ if (lastMessage) {
+ console.log(` Current: [${lastMessage.role}] "${lastMessage.content}"`);
+ }
+}
+
+function delay(ms: number): Promise {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+// Run the example if this file is executed directly
+if (require.main === module) {
+ Promise.resolve()
+ .then(() => frontendIntegrationExample())
+ .then(() => ReactComponentExample())
+ .then(() => VueComponentExample())
+ .catch(console.error);
+}
+
+export { frontendIntegrationExample };
\ No newline at end of file
diff --git a/ts/examples/05-article-generation.ts b/ts/examples/05-article-generation.ts
new file mode 100644
index 0000000..190527d
--- /dev/null
+++ b/ts/examples/05-article-generation.ts
@@ -0,0 +1,237 @@
+/**
+ * Article Generation Example
+ *
+ * This example demonstrates a complete article generation scenario
+ * using LangDiff with both streaming parsing and change tracking.
+ * It simulates generating a multi-section article with an LLM.
+ */
+
+import { z } from 'zod';
+import {
+ StreamingString,
+ Parser,
+ trackChange,
+ applyChange,
+ Operation,
+ fromZod,
+} from '../src';
+
+interface Section {
+ title: string;
+ content: string;
+ done: boolean;
+}
+
+interface Article {
+ sections: Section[];
+}
+
+// Define Zod schema for article generation response
+const ArticleGenerationSchema = z.object({
+ sectionTitles: z.array(z.string()),
+ sectionContents: z.array(z.string()),
+});
+
+// Generate StreamingObject class from Zod schema
+const ArticleGenerationResponse = fromZod(ArticleGenerationSchema);
+
+/**
+ * Simulate streaming from an LLM server
+ */
+async function* serverStream(_prompt: string): AsyncGenerator {
+ // Create initial article with some sections pre-allocated
+ const initialArticle: Article = {
+ sections: [
+ { title: '', content: '', done: false },
+ { title: '', content: '', done: false },
+ { title: '', content: '', done: false },
+ ]
+ };
+
+ const [ui, diffBuf] = trackChange(initialArticle);
+ const result = ArticleGenerationResponse.create();
+
+ // Set up event handlers for section titles
+ result.sectionTitles.onAppend((title: StreamingString, index: number) => {
+ title.onAppend((chunk: string) => {
+ ui.sections[index].title += chunk;
+ });
+ });
+
+ // Set up event handlers for section contents
+ result.sectionContents.onAppend((content: StreamingString, index: number) => {
+ content.onAppend((chunk: string) => {
+ ui.sections[index].content += chunk;
+ });
+
+ content.onComplete(() => {
+ ui.sections[index].done = true;
+ });
+ });
+
+ // Use OpenAI API for real streaming (commented out for demo purposes)
+ /*
+ import { zodResponseFormat } from 'openai/helpers/zod';
+
+ const client = new OpenAI({
+ apiKey: process.env.OPENAI_API_KEY,
+ });
+
+ const stream = await client.chat.completions.create({
+ model: 'gpt-4',
+ messages: [
+ { role: 'user', content: prompt },
+ ],
+ stream: true,
+ response_format: zodResponseFormat(ArticleGenerationSchema, 'article_generation'),
+ });
+
+ const parser = new Parser(result);
+
+ for await (const chunk of stream) {
+ const content = chunk.choices[0]?.delta?.content;
+ if (content) {
+ parser.push(content);
+ const changes = diffBuf.flush();
+ if (changes.length > 0) {
+ yield changes;
+ }
+ }
+ }
+
+ parser.complete();
+ const finalChanges = diffBuf.flush();
+ if (finalChanges.length > 0) {
+ yield finalChanges;
+ }
+ */
+
+ // Simulate streaming JSON data for demo purposes
+ const mockStreamData = [
+ '{"sectionTitles": ["',
+ 'Getting',
+ ' Started", "',
+ 'Project',
+ ' Setup", "',
+ 'Publishing"],',
+ ' "sectionContents": ["',
+ 'First,',
+ ' you need to',
+ ' create a new',
+ ' TypeScript project.',
+ ' Initialize with npm',
+ ' and configure tsconfig.',
+ '", "',
+ 'Set up your',
+ ' build pipeline.',
+ ' Configure Jest',
+ ' for testing.',
+ '", "',
+ 'Publish to npm',
+ ' registry.',
+ ' Tag your releases',
+ ' properly."]}',
+ ];
+
+ const parser = new Parser(result);
+
+ for (const chunk of mockStreamData) {
+ parser.push(chunk);
+ const changes = diffBuf.flush();
+ if (changes.length > 0) {
+ yield changes;
+ }
+ // Simulate network delay
+ await new Promise(resolve => setTimeout(resolve, 100));
+ }
+
+ parser.complete();
+ const finalChanges = diffBuf.flush();
+ if (finalChanges.length > 0) {
+ yield finalChanges;
+ }
+}
+
+/**
+ * Render the article to console
+ */
+function render(article: Article, _final = false): void {
+ let buf = '\x1b[H\x1b[J'; // Clear the console (equivalent to "\033[H\033[J")
+
+ for (const section of article.sections) {
+ // Title with formatting
+ buf += '\x1b[1m'; // Bold
+ buf += section.title;
+ if (section.done) {
+ buf += '\x1b[0;32m ā done'; // Green "done" indicator
+ }
+ buf += '\x1b[0m\n'; // Reset formatting and newline
+
+ // Content
+ if (section.done) {
+ buf += section.content;
+ } else if (section.content) {
+ // Show content with blinking cursor on last character
+ const content = section.content;
+ const lastChar = content[content.length - 1];
+ const beforeLast = content.slice(0, -1);
+ buf += beforeLast + `\x1b[7;32m${lastChar}\x1b[0m`;
+ }
+ buf += '\n\n';
+ }
+
+ process.stdout.write(buf);
+}
+
+/**
+ * Main function
+ */
+async function articleGenerationExample(): Promise {
+ console.log('š° Article Generation Example\n');
+ console.log('This example demonstrates streaming article generation with LangDiff.\n');
+
+ const prompt = 'Write me a guide to open source a TypeScript library in 3 sections without numbering. Section content should be 3 lines. Be simple and concise.';
+
+ const article: Article = {
+ sections: [
+ { title: '', content: '', done: false },
+ { title: '', content: '', done: false },
+ { title: '', content: '', done: false },
+ ]
+ };
+
+ console.log('š Starting article generation...\n');
+ render(article);
+
+ try {
+ for await (const changes of serverStream(prompt)) {
+ applyChange(article, changes);
+ render(article);
+ }
+
+ render(article, true);
+ console.log('\nā
Article generation completed!');
+
+ // Show final result
+ console.log('\nš Final Article:');
+ article.sections.forEach((section, index) => {
+ console.log(`\n${index + 1}. ${section.title}`);
+ console.log(section.content);
+ });
+
+ } catch (error) {
+ console.error('ā Error:', error);
+ }
+
+ // Keep the final result visible
+ setTimeout(() => {
+ console.log('\nš Demo completed!');
+ }, 1000);
+}
+
+// Run the example if this file is executed directly
+if (require.main === module) {
+ articleGenerationExample().catch(console.error);
+}
+
+export { articleGenerationExample };
\ No newline at end of file
diff --git a/ts/examples/README.md b/ts/examples/README.md
new file mode 100644
index 0000000..5ca9225
--- /dev/null
+++ b/ts/examples/README.md
@@ -0,0 +1,163 @@
+# LangDiff Examples
+
+This directory contains practical examples demonstrating various features and use cases of LangDiff.
+
+## Examples Overview
+
+### 01. Basic Streaming (`01-basic-streaming.ts`)
+Demonstrates fundamental streaming parser capabilities with a simple shopping list example.
+
+**Key concepts:**
+- Creating streaming objects
+- Setting up event handlers (`onAppend`, `onComplete`)
+- Progressive JSON parsing
+
+```bash
+npx ts-node examples/01-basic-streaming.ts
+```
+
+### 02. Change Tracking (`02-change-tracking.ts`)
+Shows how to track object mutations and generate JSON Patch operations.
+
+**Key concepts:**
+- Using `trackChange()` to monitor mutations
+- Generating JSON Patch diffs
+- Applying changes to remote objects
+
+```bash
+npx ts-node examples/02-change-tracking.ts
+```
+
+### 03. OpenAI Integration (`03-openai-integration.ts`)
+Demonstrates real-world integration with OpenAI's streaming API for AI applications.
+
+**Key concepts:**
+- Streaming LLM responses
+- Complex nested object structures
+- Real-time AI application patterns
+
+```bash
+# With OpenAI API key
+OPENAI_API_KEY=your-key-here npx ts-node examples/03-openai-integration.ts
+
+# With mock data (no API key required)
+npx ts-node examples/03-openai-integration.ts
+```
+
+### 04. Frontend Integration (`04-frontend-integration.ts`)
+Shows how to integrate LangDiff with frontend frameworks using Server-Sent Events.
+
+**Key concepts:**
+- SSE-based streaming
+- React/Vue integration patterns
+- Efficient state synchronization
+
+```bash
+npx ts-node examples/04-frontend-integration.ts
+```
+
+### 05. Article Generation (`05-article-generation.ts`)
+Complete article generation scenario demonstrating both streaming parsing and change tracking together.
+
+**Key concepts:**
+- Multi-section article generation
+- Combined streaming and change tracking
+- Real-time UI updates with console rendering
+- Mock LLM streaming simulation
+
+```bash
+npx ts-node examples/05-article-generation.ts
+```
+
+## Running All Examples
+
+To run all examples in sequence:
+
+```bash
+npm run examples
+```
+
+## Prerequisites
+
+Make sure you have installed the dependencies:
+
+```bash
+npm install
+```
+
+For the OpenAI example, you'll need an API key (optional - works with mock data):
+
+```bash
+export OPENAI_API_KEY=your-key-here
+```
+
+## Example Patterns
+
+### Basic Streaming Pattern
+
+```typescript
+import { StreamingObject, StreamingString, Parser } from '@langdiff/langdiff';
+
+class MyResponse extends StreamingObject {
+ message!: StreamingString;
+
+ protected _initializeFields(): void {
+ this.addField('message', new StreamingString());
+ }
+}
+
+const response = new MyResponse();
+response.message.onAppend(chunk => console.log(chunk));
+
+const parser = new Parser(response);
+parser.push('{"message": "Hello, ');
+parser.push('world!"}');
+parser.complete();
+```
+
+### Change Tracking Pattern
+
+```typescript
+import { trackChange, applyChange } from '@langdiff/langdiff';
+
+const [state, diffBuffer] = trackChange({ items: [] });
+state.items.push('new item');
+
+const changes = diffBuffer.flush();
+// changes: [{ op: 'add', path: '/items/-', value: 'new item' }]
+
+// Apply to another object
+const remoteState = { items: [] };
+applyChange(remoteState, changes);
+```
+
+### Server-Sent Events Pattern
+
+```typescript
+// Server side
+const [state, diffBuffer] = trackChange(initialState);
+response.message.onAppend(() => {
+ const changes = diffBuffer.flush();
+ res.write(`data: ${JSON.stringify({ changes })}\n\n`);
+});
+
+// Client side
+const eventSource = new EventSource('/api/stream');
+eventSource.onmessage = (event) => {
+ const { changes } = JSON.parse(event.data);
+ applyChange(clientState, changes);
+};
+```
+
+## Advanced Topics
+
+- **Performance**: Change tracking is optimized for large objects
+- **Memory**: Streaming objects handle partial states efficiently
+- **Error Handling**: All examples include proper error handling patterns
+- **Type Safety**: Full TypeScript support with generics
+
+## Need Help?
+
+- Check the main [README.md](../README.md) for detailed API documentation
+- Look at the [tests](../tests/) directory for more usage examples
+- Review the source code in [src](../src/) for implementation details
\ No newline at end of file
diff --git a/ts/examples/index.ts b/ts/examples/index.ts
new file mode 100644
index 0000000..081eeca
--- /dev/null
+++ b/ts/examples/index.ts
@@ -0,0 +1,103 @@
+/**
+ * Examples Index
+ *
+ * This file runs all examples in sequence to demonstrate
+ * the full capabilities of LangDiff.
+ */
+
+import { basicStreamingExample } from './01-basic-streaming';
+import { changeTrackingExample, incrementalChangeExample } from './02-change-tracking';
+import { openaiStreamingExample, webServerExample } from './03-openai-integration';
+import { frontendIntegrationExample } from './04-frontend-integration';
+
+async function runAllExamples() {
+ console.log('š LangDiff Examples Demo\n');
+ console.log('='.repeat(50));
+
+ try {
+ // Basic Streaming
+ console.log('\nš¦ Example 1: Basic Streaming Parser');
+ console.log('-'.repeat(40));
+ await basicStreamingExample();
+ await delay(1000);
+
+ // Change Tracking
+ console.log('\nš¦ Example 2: Change Tracking');
+ console.log('-'.repeat(40));
+ await changeTrackingExample();
+ await delay(500);
+ await incrementalChangeExample();
+ await delay(1000);
+
+ // OpenAI Integration
+ console.log('\nš¦ Example 3: OpenAI Integration');
+ console.log('-'.repeat(40));
+ await openaiStreamingExample();
+ await delay(500);
+ await webServerExample();
+ await delay(1000);
+
+ // Frontend Integration
+ console.log('\nš¦ Example 4: Frontend Integration');
+ console.log('-'.repeat(40));
+ await frontendIntegrationExample();
+ await delay(1000);
+
+ console.log('\nš All examples completed successfully!');
+ console.log('='.repeat(50));
+
+ } catch (error) {
+ console.error('\nā Example failed:', error);
+ process.exit(1);
+ }
+}
+
+function delay(ms: number): Promise {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+// Interactive menu for running individual examples
+async function interactiveMenu() {
+ const examples = [
+ { name: 'Basic Streaming Parser', fn: basicStreamingExample },
+ { name: 'Change Tracking', fn: async () => {
+ await changeTrackingExample();
+ await incrementalChangeExample();
+ }},
+ { name: 'OpenAI Integration', fn: async () => {
+ await openaiStreamingExample();
+ await webServerExample();
+ }},
+ { name: 'Frontend Integration', fn: frontendIntegrationExample },
+ { name: 'Run All Examples', fn: runAllExamples },
+ ];
+
+ console.log('šÆ LangDiff Examples Menu\n');
+ examples.forEach((example, index) => {
+ console.log(`${index + 1}. ${example.name}`);
+ });
+
+ console.log('\nTo run a specific example:');
+ console.log('npx ts-node examples/01-basic-streaming.ts');
+ console.log('npx ts-node examples/02-change-tracking.ts');
+ console.log('npx ts-node examples/03-openai-integration.ts');
+ console.log('npx ts-node examples/04-frontend-integration.ts');
+ console.log('\nTo run all examples:');
+ console.log('npx ts-node examples/index.ts');
+}
+
+// Check if we're being run directly
+if (require.main === module) {
+ const args = process.argv.slice(2);
+
+ if (args.includes('--help') || args.includes('-h')) {
+ interactiveMenu();
+ } else {
+ runAllExamples().catch(console.error);
+ }
+}
+
+export {
+ runAllExamples,
+ interactiveMenu,
+};
\ No newline at end of file
diff --git a/ts/jest.config.js b/ts/jest.config.js
new file mode 100644
index 0000000..08411a7
--- /dev/null
+++ b/ts/jest.config.js
@@ -0,0 +1,19 @@
+module.exports = {
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ roots: ['/src', '/tests'],
+ testMatch: [
+ '**/__tests__/**/*.ts',
+ '**/?(*.)+(spec|test).ts'
+ ],
+ transform: {
+ '^.+\\.ts$': ['ts-jest', {
+ isolatedModules: true,
+ }],
+ },
+ collectCoverageFrom: [
+ 'src/**/*.ts',
+ '!src/**/*.d.ts',
+ ],
+ moduleFileExtensions: ['ts', 'js', 'json'],
+};
diff --git a/ts/package-lock.json b/ts/package-lock.json
new file mode 100644
index 0000000..19440b3
--- /dev/null
+++ b/ts/package-lock.json
@@ -0,0 +1,4127 @@
+{
+ "name": "langdiff",
+ "version": "0.2.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "langdiff",
+ "version": "0.2.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/fast-json-patch": "^1.0.4",
+ "fast-json-patch": "^3.1.1",
+ "openai-partial-stream": "^0.3.9",
+ "zod": "^3.25.76"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.5.12",
+ "@types/node": "^20.11.30",
+ "jest": "^29.7.0",
+ "openai": "^4.28.4",
+ "ts-jest": "^29.1.2",
+ "ts-node": "^10.9.2",
+ "typescript": "^5.4.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
+ "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz",
+ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
+ "dev": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.3",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.3",
+ "@babel/parser": "^7.28.3",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.3",
+ "@babel/types": "^7.28.2",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
+ "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.28.3",
+ "@babel/types": "^7.28.2",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz",
+ "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
+ "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz",
+ "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
+ "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
+ "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
+ "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.3",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.3",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.2",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
+ "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jest/console": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
+ "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/core": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz",
+ "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/reporters": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-changed-files": "^29.7.0",
+ "jest-config": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-resolve-dependencies": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/environment": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
+ "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
+ "dev": true,
+ "dependencies": {
+ "expect": "^29.7.0",
+ "jest-snapshot": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
+ "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
+ "dev": true,
+ "dependencies": {
+ "jest-get-type": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
+ "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@sinonjs/fake-timers": "^10.0.2",
+ "@types/node": "*",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
+ "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "jest-mock": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
+ "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
+ "dev": true,
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "exit": "^0.1.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^4.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.1",
+ "strip-ansi": "^6.0.0",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/source-map": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz",
+ "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "callsites": "^3.0.0",
+ "graceful-fs": "^4.2.9"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
+ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "collect-v8-coverage": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz",
+ "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/test-result": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
+ "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "babel-plugin-istanbul": "^6.1.1",
+ "chalk": "^4.0.0",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pirates": "^4.0.4",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^4.0.2"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+ "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.8",
+ "chalk": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.30",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
+ "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true
+ },
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "dev": true,
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
+ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "dev": true,
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
+ "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "dev": true
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/fast-json-patch": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@types/fast-json-patch/-/fast-json-patch-1.0.4.tgz",
+ "integrity": "sha512-zdcBTP+fjd9lf0z1TctPMKZPi7wXTYNnSdk1vkzW4+MON1pk124x6je6SldFS/TzAxz0KPeukqbdATRGLG3BWg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/graceful-fs": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
+ "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jest": {
+ "version": "29.5.14",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz",
+ "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==",
+ "dev": true,
+ "dependencies": {
+ "expect": "^29.0.0",
+ "pretty-format": "^29.0.0"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.11",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz",
+ "integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/node-fetch": {
+ "version": "2.6.13",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
+ "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
+ "dependencies": {
+ "@types/node": "*",
+ "form-data": "^4.0.4"
+ }
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.33",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
+ "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
+ "dev": true,
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/agentkeepalive": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
+ "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
+ "dependencies": {
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/babel-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
+ "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/transform": "^29.7.0",
+ "@types/babel__core": "^7.1.14",
+ "babel-plugin-istanbul": "^6.1.1",
+ "babel-preset-jest": "^29.6.3",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.8.0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
+ "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-instrument": "^5.0.4",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
+ "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.12.3",
+ "@babel/parser": "^7.14.7",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
+ "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.3.3",
+ "@babel/types": "^7.3.3",
+ "@types/babel__core": "^7.1.14",
+ "@types/babel__traverse": "^7.0.6"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
+ "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
+ "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
+ "dev": true,
+ "dependencies": {
+ "babel-plugin-jest-hoist": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.25.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz",
+ "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001735",
+ "electron-to-chromium": "^1.5.204",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001735",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz",
+ "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ]
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
+ "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
+ "dev": true
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
+ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
+ "dev": true
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true
+ },
+ "node_modules/create-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
+ "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "prompts": "^2.0.1"
+ },
+ "bin": {
+ "create-jest": "bin/create-jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/dedent": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz",
+ "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==",
+ "dev": true,
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.207",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.207.tgz",
+ "integrity": "sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==",
+ "dev": true
+ },
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/expect-utils": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/fast-json-patch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz",
+ "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ=="
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/form-data-encoder": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
+ "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
+ },
+ "node_modules/formdata-node": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
+ "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
+ "dependencies": {
+ "node-domexception": "1.0.0",
+ "web-streams-polyfill": "4.0.0-beta.3"
+ },
+ "engines": {
+ "node": ">= 12.20"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
+ "node_modules/handlebars": {
+ "version": "4.7.8",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
+ "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.2",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "dev": true,
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-instrument/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
+ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0",
+ "source-map": "^0.6.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
+ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
+ "dev": true,
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
+ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/core": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "import-local": "^3.0.2",
+ "jest-cli": "^29.7.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
+ "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==",
+ "dev": true,
+ "dependencies": {
+ "execa": "^5.0.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz",
+ "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "co": "^4.6.0",
+ "dedent": "^1.0.0",
+ "is-generator-fn": "^2.0.0",
+ "jest-each": "^29.7.0",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "^29.7.0",
+ "pure-rand": "^6.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-cli": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz",
+ "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==",
+ "dev": true,
+ "dependencies": {
+ "@jest/core": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "create-jest": "^29.7.0",
+ "exit": "^0.1.2",
+ "import-local": "^3.0.2",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "yargs": "^17.3.1"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
+ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/test-sequencer": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-jest": "^29.7.0",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "deepmerge": "^4.2.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-circus": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "parse-json": "^5.2.0",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-diff": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
+ "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^29.6.3",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-docblock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz",
+ "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==",
+ "dev": true,
+ "dependencies": {
+ "detect-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz",
+ "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
+ "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-get-type": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
+ "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
+ "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/graceful-fs": "^4.1.3",
+ "@types/node": "*",
+ "anymatch": "^3.0.3",
+ "fb-watchman": "^2.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.2"
+ }
+ },
+ "node_modules/jest-leak-detector": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
+ "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==",
+ "dev": true,
+ "dependencies": {
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
+ "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
+ "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.12.13",
+ "@jest/types": "^29.6.3",
+ "@types/stack-utils": "^2.0.0",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-mock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
+ "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
+ "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz",
+ "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-pnp-resolver": "^1.2.2",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "resolve": "^1.20.0",
+ "resolve.exports": "^2.0.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz",
+ "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==",
+ "dev": true,
+ "dependencies": {
+ "jest-regex-util": "^29.6.3",
+ "jest-snapshot": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz",
+ "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/environment": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "graceful-fs": "^4.2.9",
+ "jest-docblock": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-leak-detector": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-resolve": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz",
+ "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/globals": "^29.7.0",
+ "@jest/source-map": "^29.6.3",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "cjs-module-lexer": "^1.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz",
+ "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@babel/generator": "^7.7.2",
+ "@babel/plugin-syntax-jsx": "^7.7.2",
+ "@babel/plugin-syntax-typescript": "^7.7.2",
+ "@babel/types": "^7.3.3",
+ "@jest/expect-utils": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0",
+ "chalk": "^4.0.0",
+ "expect": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "natural-compare": "^1.4.0",
+ "pretty-format": "^29.7.0",
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+ "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "graceful-fs": "^4.2.9",
+ "picomatch": "^2.2.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
+ "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "camelcase": "^6.2.0",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "leven": "^3.1.0",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watcher": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
+ "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
+ "dev": true,
+ "dependencies": {
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "jest-util": "^29.7.0",
+ "string-length": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+ "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "jest-util": "^29.7.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+ "dev": true
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true
+ },
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/openai": {
+ "version": "4.104.0",
+ "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz",
+ "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==",
+ "dependencies": {
+ "@types/node": "^18.11.18",
+ "@types/node-fetch": "^2.6.4",
+ "abort-controller": "^3.0.0",
+ "agentkeepalive": "^4.2.1",
+ "form-data-encoder": "1.7.2",
+ "formdata-node": "^4.3.2",
+ "node-fetch": "^2.6.7"
+ },
+ "bin": {
+ "openai": "bin/cli"
+ },
+ "peerDependencies": {
+ "ws": "^8.18.0",
+ "zod": "^3.23.8"
+ },
+ "peerDependenciesMeta": {
+ "ws": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/openai-partial-stream": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/openai-partial-stream/-/openai-partial-stream-0.3.9.tgz",
+ "integrity": "sha512-6qmF8Ni3IXWFAh0mUgDSmmpeMIrcEvcWR66QmBGaV9XtB4vqTWoVPNUFBViLIJd2l0shaYM5E05502tGZvIzUA==",
+ "dependencies": {
+ "openai": "^4.26.0",
+ "zod": "^3.22.3",
+ "zod-to-json-schema": "^3.21.4"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ }
+ },
+ "node_modules/openai/node_modules/@types/node": {
+ "version": "18.19.123",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.123.tgz",
+ "integrity": "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/openai/node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-locate/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "dev": true,
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ]
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve.exports": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz",
+ "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "dev": true
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
+ "node_modules/ts-jest": {
+ "version": "29.4.1",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.1.tgz",
+ "integrity": "sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==",
+ "dev": true,
+ "dependencies": {
+ "bs-logger": "^0.2.6",
+ "fast-json-stable-stringify": "^2.1.0",
+ "handlebars": "^4.7.8",
+ "json5": "^2.2.3",
+ "lodash.memoize": "^4.1.2",
+ "make-error": "^1.3.6",
+ "semver": "^7.7.2",
+ "type-fest": "^4.41.0",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/transform": "^29.0.0 || ^30.0.0",
+ "@jest/types": "^29.0.0 || ^30.0.0",
+ "babel-jest": "^29.0.0 || ^30.0.0",
+ "jest": "^29.0.0 || ^30.0.0",
+ "jest-util": "^29.0.0 || ^30.0.0",
+ "typescript": ">=4.3 <6"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/transform": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jest-util": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-jest/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ts-jest/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "dev": true,
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
+ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uglify-js": {
+ "version": "3.19.3",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
+ "dev": true,
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true
+ },
+ "node_modules/v8-to-istanbul": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
+ "node_modules/web-streams-polyfill": {
+ "version": "4.0.0-beta.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
+ "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/write-file-atomic": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
+ "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
+ "dev": true,
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.7"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.24.6",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
+ "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
+ "peerDependencies": {
+ "zod": "^3.24.1"
+ }
+ }
+ }
+}
diff --git a/ts/package.json b/ts/package.json
new file mode 100644
index 0000000..62eb981
--- /dev/null
+++ b/ts/package.json
@@ -0,0 +1,61 @@
+{
+ "name": "@langdiff/langdiff",
+ "version": "0.2.0",
+ "description": "LangDiff is a TypeScript library that solves the hard problems of streaming structured LLM outputs to frontends.",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "files": [
+ "dist/**/*",
+ "README.md"
+ ],
+ "scripts": {
+ "build": "tsc",
+ "test": "jest",
+ "test:watch": "jest --watch",
+ "dev": "tsc --watch",
+ "examples": "ts-node examples/index.ts",
+ "examples:basic": "ts-node examples/01-basic-streaming.ts",
+ "examples:tracking": "ts-node examples/02-change-tracking.ts",
+ "examples:openai": "ts-node examples/03-openai-integration.ts",
+ "examples:frontend": "ts-node examples/04-frontend-integration.ts",
+ "examples:article": "ts-node examples/05-article-generation.ts"
+ },
+ "keywords": [
+ "llm",
+ "streaming",
+ "json",
+ "parser",
+ "diff",
+ "typescript"
+ ],
+ "author": "Global AI Platform ",
+ "maintainers": [
+ "Taeho Kim ",
+ "Hanju Jo "
+ ],
+ "license": "Apache-2.0",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/globalaiplatform/langdiff",
+ "directory": "ts"
+ },
+ "homepage": "https://github.com/globalaiplatform/langdiff/tree/main/ts",
+ "dependencies": {
+ "@types/fast-json-patch": "^1.0.4",
+ "fast-json-patch": "^3.1.1",
+ "openai-partial-stream": "^0.3.9",
+ "zod": "^3.25.76"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.5.12",
+ "@types/node": "^20.11.30",
+ "jest": "^29.7.0",
+ "openai": "^4.28.4",
+ "ts-jest": "^29.1.2",
+ "ts-node": "^10.9.2",
+ "typescript": "^5.4.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+}
diff --git a/ts/src/index.ts b/ts/src/index.ts
new file mode 100644
index 0000000..15eeabc
--- /dev/null
+++ b/ts/src/index.ts
@@ -0,0 +1,44 @@
+// Parser exports
+export {
+ StreamingValue,
+ StreamingString,
+ StreamingList,
+ Atom,
+ StreamingObject,
+} from './parser/model';
+
+// Schema builder functions (use with: import * as ld from '@langdiff/langdiff')
+export {
+ string,
+ array,
+ number,
+ boolean,
+ atom,
+ object,
+ infer,
+} from './parser/schema';
+
+export {
+ fromZod,
+} from './parser/from-zod';
+
+export {
+ Parser,
+} from './parser/parser';
+
+// Type aliases for convenience
+export {
+ String,
+ Object,
+} from './parser/model';
+
+// Tracker exports
+export {
+ ChangeTracker,
+ DiffBuffer,
+ Operation,
+ JSONPatchChangeTracker,
+ EfficientJSONPatchChangeTracker,
+ trackChange,
+ applyChange,
+} from './tracker';
diff --git a/ts/src/parser/from-zod.ts b/ts/src/parser/from-zod.ts
new file mode 100644
index 0000000..432c929
--- /dev/null
+++ b/ts/src/parser/from-zod.ts
@@ -0,0 +1,71 @@
+import { z } from "zod";
+import { array, ArraySchema, atom, AtomArraySchema, AtomSchema, BaseSchema, ObjectSchema, ObjectSchemaInstance, string, StringSchema } from "./schema";
+import { StreamingString } from "./model";
+
+/**
+ * Helper function to create streaming fields from Zod types
+ */
+export function fromZod(zodType: T): ZodTypeToStreamingType {
+ const fromZodInternal = () => {
+ // Handle optional and nullable wrappers first
+ let unwrapped = zodType;
+ while (unwrapped instanceof z.ZodOptional || unwrapped instanceof z.ZodNullable) {
+ unwrapped = unwrapped.unwrap();
+ }
+
+ if (unwrapped instanceof z.ZodString) {
+ return string();
+ }
+
+ if (unwrapped instanceof z.ZodArray) {
+ if (unwrapped.element instanceof z.ZodString) {
+ return array(string());
+ }
+ if (unwrapped.element instanceof z.ZodObject) {
+ return array(fromZodObject(unwrapped.element));
+ }
+ return array(unwrapped.element);
+ }
+
+ if (unwrapped instanceof z.ZodObject) {
+ return fromZodObject(unwrapped);
+ }
+
+ return atom(zodType);
+ };
+ return fromZodInternal().withZodSchema(zodType) as ZodTypeToStreamingType;
+}
+
+function fromZodObject(
+ zodSchema: z.ZodObject
+): ObjectSchema> {
+ const shape = zodSchema.shape;
+
+ const fields: {[key: string]: BaseSchema} = {};
+ for (const [key, zodType] of Object.entries(shape)) {
+ fields[key] = fromZod(zodType);
+ }
+
+ return new ObjectSchema(fields) as any;
+}
+
+/**
+ * Type mapping from Zod types to Streaming types
+ */
+type ZodToStreamingFields = {
+ [K in keyof T]: ZodTypeToStreamingType
+}
+
+type ZodTypeToStreamingType =
+ T extends z.ZodString ? StringSchema :
+ T extends z.ZodNumber ? AtomSchema :
+ T extends z.ZodBoolean ? AtomSchema :
+ T extends z.ZodArray ?
+ U extends z.ZodString ? ArraySchema :
+ U extends z.ZodObject ? ArraySchema>> :
+ AtomArraySchema> :
+ T extends z.ZodObject ? ObjectSchema> :
+ T extends z.ZodOptional ? ZodTypeToStreamingType :
+ T extends z.ZodNullable ? ZodTypeToStreamingType :
+ T extends z.ZodType ? AtomSchema :
+ never;
diff --git a/ts/src/parser/index.ts b/ts/src/parser/index.ts
new file mode 100644
index 0000000..e9337e9
--- /dev/null
+++ b/ts/src/parser/index.ts
@@ -0,0 +1,2 @@
+export * from './model';
+export * from './parser';
diff --git a/ts/src/parser/model.ts b/ts/src/parser/model.ts
new file mode 100644
index 0000000..1415d92
--- /dev/null
+++ b/ts/src/parser/model.ts
@@ -0,0 +1,389 @@
+import { z } from 'zod';
+import { BaseSchema } from './schema';
+
+/**
+ * Base class for streaming values that are built incrementally.
+ */
+export abstract class StreamingValue {
+ private _onStartCallbacks: Array<() => void> = [];
+ private _onCompleteCallbacks: Array<(value: TOut) => void> = [];
+ protected _started: boolean = false;
+ protected _completed: boolean = false;
+
+ /**
+ * Register a callback that is called when the streaming starts.
+ */
+ onStart(callback: () => void): void {
+ this._onStartCallbacks.push(callback);
+ }
+
+ /**
+ * Register a callback that is called when the streaming completes.
+ */
+ onComplete(callback: (value: TOut) => void): void {
+ this._onCompleteCallbacks.push(callback);
+ }
+
+ /**
+ * Trigger start callbacks if not already started.
+ */
+ protected triggerStart(): void {
+ if (!this._started) {
+ this._started = true;
+ for (const callback of this._onStartCallbacks) {
+ callback();
+ }
+ }
+ }
+
+ /**
+ * Trigger complete callbacks.
+ */
+ protected triggerComplete(value: TOut): void {
+ if (!this._completed) {
+ this._completed = true;
+ for (const callback of this._onCompleteCallbacks) {
+ callback(value);
+ }
+ }
+ }
+
+ /**
+ * Update the streaming value with new data.
+ */
+ abstract update(value: TIn): void;
+
+ /**
+ * Mark the streaming value as complete.
+ */
+ abstract complete(): void;
+}
+
+/**
+ * Represents a string that is streamed incrementally.
+ */
+export class StreamingString extends StreamingValue {
+ private _value: string | null = null;
+ private _onAppendCallbacks: Array<(chunk: string) => void> = [];
+
+ /**
+ * Register a callback that is called with each new chunk of the string.
+ */
+ onAppend(callback: (chunk: string) => void): void {
+ this._onAppendCallbacks.push(callback);
+ }
+
+ override update(value: string | null): void {
+ this.triggerStart();
+
+ let chunk: string | null;
+ if (this._value === null) {
+ chunk = value;
+ } else {
+ if (value === null || !value.startsWith(this._value)) {
+ throw new Error(
+ 'StreamingString can only be updated with a continuation of the current value.'
+ );
+ }
+ if (value.length === this._value.length) {
+ return;
+ }
+ chunk = value.slice(this._value.length);
+ }
+
+ if (chunk !== null) {
+ for (const callback of this._onAppendCallbacks) {
+ callback(chunk);
+ }
+ }
+ this._value = value;
+ }
+
+ override complete(): void {
+ this.triggerComplete(this._value);
+ }
+
+ get value(): string | null {
+ return this._value;
+ }
+}
+
+type UnwrapStreamingValue = T extends StreamingValue ? U : never;
+
+/**
+ * Represents a list that is streamed incrementally.
+ */
+export class StreamingList> extends StreamingValue[]> {
+ private _value: UnwrapStreamingValue[] = [];
+ private _itemSchema: BaseSchema;
+ private _streamingValues: T[] = [];
+ private _onAppendCallbacks: Array<(item: T, index: number) => void> = [];
+
+ constructor(itemSchema: BaseSchema) {
+ super();
+ this._itemSchema = itemSchema;
+ }
+
+ /**
+ * Register a callback that is called when a new item is appended to the list.
+ */
+ onAppend(callback: (item: T, index: number) => void): void {
+ this._onAppendCallbacks.push(callback);
+ }
+
+ override update(value: UnwrapStreamingValue[] | null): void {
+ this.triggerStart();
+ // Handle null values like Python version - treat as empty array
+ const actualValue = value || [];
+ if (actualValue.length === 0) {
+ this._value = actualValue;
+ return;
+ }
+ this._updateStreaming(actualValue);
+ }
+
+ private _updateStreaming(value: UnwrapStreamingValue[]): void {
+ const prevCount = this._value.length;
+ const count = value.length;
+
+ if (count > prevCount) {
+ // Complete previous item if exists
+ if (prevCount > 0) {
+ const s = this._streamingValues[prevCount - 1]!;
+ s.update(value[prevCount - 1]);
+ s.complete();
+ }
+
+ // Add new complete items
+ for (let i = prevCount; i < count - 1; i++) {
+ const s = this._itemSchema.create();
+ this._streamingValues.push(s);
+ for (const callback of this._onAppendCallbacks) {
+ callback(s, i);
+ }
+ s.update(value[i]);
+ s.complete();
+ }
+
+ // Add new streaming item
+ const s = this._itemSchema.create();
+ this._streamingValues.push(s);
+ for (const callback of this._onAppendCallbacks) {
+ callback(s, count - 1);
+ }
+ s.update(value[count - 1]);
+ } else {
+ // Update last item
+ const s = this._streamingValues[this._streamingValues.length - 1]!;
+ s.update(value[value.length - 1]);
+ }
+
+ this._value = value;
+ }
+
+ override complete(): void {
+ if (this._streamingValues.length > 0) {
+ const lastValue = this._streamingValues[this._streamingValues.length - 1]!;
+ lastValue.complete();
+ }
+ this.triggerComplete(this._value);
+ }
+
+ get value(): T[] {
+ return this._value;
+ }
+}
+
+/**
+ * Represents a list that is streamed incrementally.
+ */
+export class StreamingAtomList extends StreamingValue {
+ private _value: unknown[] = [];
+ private _outValues: T[] = [];
+ private _itemZodSchema: z.ZodType;
+ private _onAppendCallbacks: Array<(item: T, index: number) => void> = [];
+
+ constructor(itemZodSchema: z.ZodType) {
+ super();
+ this._itemZodSchema = itemZodSchema;
+ }
+
+ /**
+ * Register a callback that is called when a new item is appended to the list.
+ */
+ onAppend(callback: (item: T, index: number) => void): void {
+ this._onAppendCallbacks.push(callback);
+ }
+
+ override update(value: unknown[] | null): void {
+ this.triggerStart();
+ // Handle null values like Python version - treat as empty array
+ if (value === null || value.length === 0) {
+ this._value = [];
+ return;
+ }
+ this._updateComplete(value);
+ }
+
+ private _updateComplete(value: unknown[]): void {
+ const prevCount = this._value.length;
+ const count = value.length;
+
+ if (count > prevCount) {
+ const startIdx = prevCount > 0 ? prevCount - 1 : 0;
+ for (let i = startIdx; i < count - 1; i++) {
+ const s = this._itemZodSchema.parse(value[i]);
+ this._outValues.push(s);
+ for (const callback of this._onAppendCallbacks) {
+ callback(s, i);
+ }
+ }
+ }
+ this._value = value;
+ }
+
+ override complete(): void {
+ if (this._value.length > 0) {
+ const i = this._value.length - 1;
+ const s = this._itemZodSchema.parse(this._value[i]);
+ this._outValues.push(s);
+ for (const callback of this._onAppendCallbacks) {
+ callback(s, i);
+ }
+ }
+
+ this.triggerComplete(this._outValues);
+ }
+
+ get value(): T[] {
+ return this._outValues;
+ }
+}
+
+/**
+ * Represents an atomic value that is received whole, not streamed incrementally.
+ */
+export class Atom extends StreamingValue {
+ private _value?: unknown;
+ private _outValue?: T;
+
+ constructor(private schema: z.ZodType) {
+ super();
+ }
+
+ override update(value: unknown): void {
+ this.triggerStart();
+ this._value = value;
+ }
+
+ override complete(): void {
+ if (this._outValue !== undefined) {
+ return;
+ }
+ this._outValue = this.schema.parse(this._value);
+ this.triggerComplete(this._outValue);
+ }
+
+ get value(): T | undefined {
+ return this._outValue;
+ }
+}
+
+type UnwrapStreamingObject = {
+ [K in keyof T]: T[K] extends StreamingValue ? U : never
+};
+
+/**
+ * Base class for objects that are streamed incrementally.
+ */
+export class StreamingObject>> extends StreamingValue, Record> {
+ private _fields: T;
+ private _keys: (keyof T)[];
+ private _value: Record = {};
+ private _lastKeyIndex: number | null = null;
+ private _onUpdateCallbacks: Array<(value: Record) => void> = [];
+
+ constructor(fields: T) {
+ super();
+ this._fields = fields;
+ this._keys = Object.keys(fields);
+ }
+
+ /**
+ * Register a callback that is called whenever the object is updated.
+ */
+ onUpdate(callback: (value: Record) => void): void {
+ this._onUpdateCallbacks.push(callback);
+ }
+
+ update(value: Record): void {
+ this.triggerStart();
+
+ // Find the highest index of keys present in the value
+ let maxKeyIndex = -1;
+ for (let i = 0; i < this._keys.length; i++) {
+ if (this._keys[i] in value) {
+ maxKeyIndex = i;
+ }
+ }
+
+ // If no keys are found, just update the value
+ if (maxKeyIndex === -1) {
+ this._value = value;
+ for (const callback of this._onUpdateCallbacks) {
+ callback(this._value);
+ }
+ return;
+ }
+
+ // Complete previous fields if needed
+ const startIdx = this._lastKeyIndex === null ? 0 : this._lastKeyIndex + 1;
+
+ for (let i = startIdx; i <= maxKeyIndex; i++) {
+ const key = this._keys[i];
+ if (key in value) {
+ // Complete the previous field if this is not the first field
+ if (this._lastKeyIndex !== null && this._lastKeyIndex < i) {
+ const lastKey = this._keys[this._lastKeyIndex];
+ const s = this._fields[lastKey];
+ s.update(value[lastKey as string]);
+ s.complete();
+ }
+ this._lastKeyIndex = i;
+ }
+ }
+
+ // Update the current field
+ if (maxKeyIndex >= 0) {
+ const currentKey = this._keys[maxKeyIndex];
+ if (currentKey in value) {
+ const s = this._fields[currentKey];
+ s.update(value[currentKey as string]);
+ }
+ }
+
+ this._value = value;
+
+ for (const callback of this._onUpdateCallbacks) {
+ callback(this._value);
+ }
+ }
+
+ complete(): void {
+ // Complete the last active field if there was one
+ if (this._lastKeyIndex !== null) {
+ const lastKey = this._keys[this._lastKeyIndex];
+ const lastValue = this._fields[lastKey];
+ lastValue.complete();
+ }
+ this.triggerComplete(this._value as any);
+ }
+
+ getValue(): Record {
+ return this._value;
+ }
+}
+
+// Type aliases for convenience
+export type String = StreamingString;
+export type Object = StreamingObject;
diff --git a/ts/src/parser/parser.ts b/ts/src/parser/parser.ts
new file mode 100644
index 0000000..c843984
--- /dev/null
+++ b/ts/src/parser/parser.ts
@@ -0,0 +1,56 @@
+import {StreamingObject} from './model';
+import {StreamMode, StreamParser} from "openai-partial-stream";
+
+/**
+ * Parser for streaming JSON that can handle partial/incomplete JSON strings.
+ */
+export class Parser {
+ private root: StreamingObject;
+ private _streamParser: StreamParser;
+
+ constructor(root: StreamingObject) {
+ this.root = root;
+ this._streamParser = new StreamParser(StreamMode.StreamObjectKeyValueTokens);
+ }
+
+ /**
+ * Push a chunk of JSON string to the parser.
+ */
+ push(chunk: string): void {
+ if (chunk === '') {
+ return;
+ }
+
+ const parsed = this._streamParser.parse(chunk);
+ if (parsed && (parsed.status === 'COMPLETED' || parsed.status === 'PARTIAL')) {
+ this.root.update(parsed.data);
+ }
+ }
+
+ /**
+ * Mark the parsing as complete.
+ */
+ complete(): void {
+ this.root.complete();
+ }
+
+ /**
+ * Use the parser with automatic completion.
+ * Equivalent to Python's context manager behavior.
+ *
+ * @example
+ * const parser = new Parser(root);
+ * parser.use((p) => {
+ * p.push(chunk1);
+ * p.push(chunk2);
+ * // complete() is called automatically
+ * });
+ */
+ use(callback: (parser: Parser) => void): void {
+ try {
+ callback(this);
+ } finally {
+ this.complete();
+ }
+ }
+}
diff --git a/ts/src/parser/schema.ts b/ts/src/parser/schema.ts
new file mode 100644
index 0000000..457ed6c
--- /dev/null
+++ b/ts/src/parser/schema.ts
@@ -0,0 +1,175 @@
+import { z, ZodSchema } from "zod";
+import { Atom, StreamingAtomList, StreamingList, StreamingObject, StreamingString, StreamingValue } from "./model";
+
+export type infer> = T extends BaseSchema ? U : never;
+
+export abstract class BaseSchema> {
+ public description?: string;
+ public defaultValue?: any;
+ public customZodSchema?: z.ZodSchema;
+
+ abstract create(): T;
+
+ describe(description: string) {
+ if (this.customZodSchema) {
+ throw new Error("Cannot set description when Zod schema is set");
+ }
+ this.description = description;
+ return this;
+ }
+
+ default(value: any) {
+ if (this.customZodSchema) {
+ throw new Error("Cannot set default value when Zod schema is set");
+ }
+ this.defaultValue = value;
+ return this;
+ }
+
+ withZodSchema(zodSchema: z.ZodSchema) {
+ if (this.description !== undefined) {
+ throw new Error("Cannot set Zod schema when description is set");
+ }
+ if (this.defaultValue !== undefined) {
+ throw new Error("Cannot set Zod schema when default value is set");
+ }
+ this.customZodSchema = zodSchema;
+ return this;
+ }
+
+ toZod(): ZodSchema {
+ if (this.customZodSchema) {
+ return this.customZodSchema;
+ }
+ let zodSchema = this._toZod();
+ if (this.description !== undefined) {
+ zodSchema = zodSchema.describe(this.description);
+ }
+ if (this.defaultValue !== undefined) {
+ zodSchema = zodSchema.default(this.defaultValue);
+ }
+ return zodSchema;
+ }
+
+ protected abstract _toZod(): ZodSchema;
+}
+
+export class StringSchema extends BaseSchema {
+ override create(): StreamingString {
+ return new StreamingString();
+ }
+
+ override _toZod() {
+ return z.string();
+ }
+}
+
+export function string(): StringSchema {
+ return new StringSchema();
+}
+
+export class ArraySchema> extends BaseSchema> {
+ constructor(private itemSchema: BaseSchema) {
+ super();
+ }
+
+ override create(): StreamingList {
+ return new StreamingList(this.itemSchema);
+ }
+
+ override _toZod() {
+ return z.array(this.itemSchema.toZod());
+ }
+}
+
+export class AtomArraySchema extends BaseSchema> {
+ constructor(public readonly zodSchema: ZodSchema) {
+ super();
+ }
+
+ override create(): StreamingAtomList {
+ return new StreamingAtomList(this.zodSchema);
+ }
+
+ override _toZod() {
+ return z.array(this.zodSchema);
+ }
+}
+
+export function array(zodSchema: z.ZodType): AtomArraySchema;
+export function array(itemSchema: AtomSchema): AtomArraySchema;
+export function array>(itemSchema: BaseSchema): ArraySchema;
+export function array(itemSchema: any): any {
+ if (itemSchema instanceof z.ZodType) {
+ return new AtomArraySchema(itemSchema);
+ }
+ if (itemSchema instanceof AtomSchema) {
+ return new AtomArraySchema(itemSchema.zodSchema);
+ }
+ return new ArraySchema(itemSchema);
+}
+
+export class AtomSchema extends BaseSchema> {
+ constructor(public readonly zodSchema: ZodSchema) {
+ super();
+ }
+
+ override create(): Atom {
+ return new Atom(this.zodSchema);
+ }
+
+ override _toZod() {
+ return this.zodSchema;
+ }
+}
+
+export function atom(zodSchema: ZodSchema): AtomSchema {
+ return new AtomSchema(zodSchema);
+}
+
+/**
+ * Create a Atom for numbers - alias of atom(z.number())
+ */
+export function number(): AtomSchema {
+ return atom(z.number());
+}
+
+/**
+ * Create a Atom for booleans - alias of atom(z.boolean())
+ */
+export function boolean(): AtomSchema {
+ return atom(z.boolean());
+}
+
+type ObjectSchemaFields = {
+ [K in keyof T]: T[K] extends BaseSchema ? U : never
+};
+export type ObjectSchemaInstance = StreamingObject> & ObjectSchemaFields;
+
+export class ObjectSchema>> extends BaseSchema> {
+ constructor(private fields: T) {
+ super();
+ }
+
+ override create(): ObjectSchemaInstance {
+ const fields = Object.fromEntries(
+ Object.entries(this.fields).map(([key, field]) => [key, field.create()])
+ ) as ObjectSchemaFields;
+ const instance = new StreamingObject(fields);
+ // Bind fields as property for convenience
+ Object.assign(instance, fields);
+ return instance as ObjectSchemaInstance;
+ }
+
+ override _toZod() {
+ return z.object(
+ Object.fromEntries(
+ Object.entries(this.fields).map(([key, field]) => [key, field.toZod()])
+ )
+ );
+ }
+}
+
+export function object>>(fields: T): ObjectSchema {
+ return new ObjectSchema(fields);
+}
diff --git a/ts/src/tracker/change-tracker.ts b/ts/src/tracker/change-tracker.ts
new file mode 100644
index 0000000..5beccd8
--- /dev/null
+++ b/ts/src/tracker/change-tracker.ts
@@ -0,0 +1,60 @@
+/**
+ * Represents a JSON Patch operation.
+ */
+export interface Operation {
+ op: string;
+ path: string;
+ value?: any;
+ from?: string;
+}
+
+/**
+ * Base class for change tracking implementations.
+ */
+export abstract class ChangeTracker {
+ protected changes: Operation[] = [];
+
+ /**
+ * Track changes to an object and return a proxy.
+ */
+ abstract track(obj: T): T;
+
+ /**
+ * Get and clear accumulated changes.
+ */
+ flush(): Operation[] {
+ const result = [...this.changes];
+ this.changes = [];
+ return result;
+ }
+
+ /**
+ * Get accumulated changes without clearing them.
+ */
+ getChanges(): Operation[] {
+ return [...this.changes];
+ }
+
+ /**
+ * Clear accumulated changes.
+ */
+ clear(): void {
+ this.changes = [];
+ }
+
+ /**
+ * Add a change operation.
+ */
+ protected addChange(operation: Operation): void {
+ this.changes.push(operation);
+ }
+}
+
+/**
+ * Interface for objects that can provide a diff buffer.
+ */
+export interface DiffBuffer {
+ flush(): Operation[];
+ getChanges(): Operation[];
+ clear(): void;
+}
diff --git a/ts/src/tracker/changes.ts b/ts/src/tracker/changes.ts
new file mode 100644
index 0000000..2503676
--- /dev/null
+++ b/ts/src/tracker/changes.ts
@@ -0,0 +1,77 @@
+import { Operation } from './change-tracker';
+
+// Use require to avoid TypeScript import issues
+const jsonPatch = require('fast-json-patch');
+
+/**
+ * JSON Patch Operation interface
+ */
+interface JsonPatchOperation {
+ op: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test';
+ path: string;
+ value?: any;
+ from?: string;
+}
+
+/**
+ * Apply JSON Patch operations to an object.
+ */
+export function applyChange(obj: any, operations: Operation[]): void {
+ const standardOps: JsonPatchOperation[] = [];
+
+ for (const op of operations) {
+ if (op.op === 'append') {
+ // Handle custom append operation for string concatenation
+ applyAppendOperation(obj, op);
+ } else {
+ // Convert to standard JSON Patch operation
+ const standardOp: JsonPatchOperation = {
+ op: op.op as any,
+ path: op.path,
+ value: op.value,
+ };
+ if (op.from) {
+ standardOp.from = op.from;
+ }
+ standardOps.push(standardOp);
+ }
+ }
+
+ if (standardOps.length > 0) {
+ jsonPatch.applyPatch(obj, standardOps);
+ }
+}
+
+/**
+ * Apply a custom append operation for string concatenation.
+ */
+function applyAppendOperation(obj: any, operation: Operation): void {
+ const path = operation.path;
+ const value = operation.value;
+
+ if (!path.startsWith('/')) {
+ throw new Error('Invalid path: must start with "/"');
+ }
+
+ const pathParts = path.slice(1).split('/');
+ let current = obj;
+
+ // Navigate to the parent object
+ for (let i = 0; i < pathParts.length - 1; i++) {
+ const part = pathParts[i];
+ current = current[part];
+ if (current === undefined) {
+ throw new Error(`Path not found: ${path}`);
+ }
+ }
+
+ const lastPart = pathParts[pathParts.length - 1];
+
+ if (typeof current[lastPart] !== 'string') {
+ throw new Error('Append operation can only be applied to strings');
+ }
+
+ current[lastPart] += value;
+}
+
+export { Operation };
diff --git a/ts/src/tracker/impl.ts b/ts/src/tracker/impl.ts
new file mode 100644
index 0000000..0af97d9
--- /dev/null
+++ b/ts/src/tracker/impl.ts
@@ -0,0 +1,317 @@
+import { ChangeTracker, Operation } from './change-tracker';
+
+/**
+ * Base class for JSON Patch change trackers with common proxy logic.
+ */
+abstract class BaseJSONPatchChangeTracker extends ChangeTracker {
+ track(obj: T): T {
+ return this.createProxy(obj, '');
+ }
+
+ private createProxy(obj: T, path: string): T {
+ if (obj === null || typeof obj !== 'object') {
+ return obj;
+ }
+
+ if (Array.isArray(obj)) {
+ return this.createArrayProxy(obj, path) as unknown as T;
+ }
+
+ return this.createObjectProxy(obj as Record, path) as unknown as T;
+ }
+
+ private createObjectProxy(obj: Record, path: string): Record {
+ const tracker = this;
+
+ // First, wrap all existing properties that are objects or arrays
+ for (const [key, value] of Object.entries(obj)) {
+ if (value !== null && typeof value === 'object') {
+ const propPath = path + '/' + key;
+ obj[key] = tracker.createProxy(value, propPath);
+ }
+ }
+
+ return new Proxy(obj, {
+ set(target, property, value, receiver) {
+ const propPath = path + '/' + String(property);
+ const oldValue = target[property as string];
+
+ tracker.handleObjectPropertyChange(oldValue, value, propPath);
+
+ // Wrap the new value in a proxy if it's an object
+ const proxiedValue = tracker.createProxy(value, propPath);
+ return Reflect.set(target, property, proxiedValue, receiver);
+ },
+
+ deleteProperty(target, property) {
+ const propPath = path + '/' + String(property);
+ tracker.addChange({
+ op: 'remove',
+ path: propPath,
+ });
+ return Reflect.deleteProperty(target, property);
+ },
+ });
+ }
+
+ /**
+ * Handle array element change - to be implemented by subclasses
+ */
+ protected abstract handleArrayElementChange(
+ target: any[],
+ index: number,
+ newValue: any,
+ propPath: string
+ ): void;
+
+ /**
+ * Handle object property change - to be implemented by subclasses
+ */
+ protected abstract handleObjectPropertyChange(
+ oldValue: any,
+ newValue: any,
+ propPath: string
+ ): void;
+
+ private createArrayProxy(arr: any[], path: string): any[] {
+ const tracker = this;
+
+ // First, wrap all existing array elements that are objects
+ for (let i = 0; i < arr.length; i++) {
+ if (arr[i] !== null && typeof arr[i] === 'object') {
+ const itemPath = path + '/' + i;
+ arr[i] = tracker.createProxy(arr[i], itemPath);
+ }
+ }
+
+ return new Proxy(arr, {
+ set(target, property, value, receiver) {
+ if (property === 'length') {
+ return Reflect.set(target, property, value, receiver);
+ }
+
+ const index = Number(property);
+ if (isNaN(index)) {
+ return Reflect.set(target, property, value, receiver);
+ }
+
+ const propPath = path + '/' + String(property);
+
+ tracker.handleArrayElementChange(target, index, value, propPath);
+
+ // Wrap the new value in a proxy if it's an object
+ const proxiedValue = tracker.createProxy(value, propPath);
+ return Reflect.set(target, property, proxiedValue, receiver);
+ },
+
+ get(target, property, receiver) {
+ if (property === 'push') {
+ return function(...items: any[]) {
+ const startIndex = target.length;
+ const result = Array.prototype.push.apply(target, items);
+
+ // Track each pushed item
+ items.forEach((item, i) => {
+ const itemIndex = startIndex + i;
+ const itemPath = path + '/' + itemIndex;
+ tracker.addChange({
+ op: 'add',
+ path: path + '/-',
+ value: item,
+ });
+
+ // Replace the item with a proxy version
+ const proxiedItem = tracker.createProxy(item, itemPath);
+ target[itemIndex] = proxiedItem;
+ });
+
+ return result;
+ };
+ }
+
+ if (property === 'pop') {
+ return function() {
+ const lastIndex = target.length - 1;
+ if (lastIndex >= 0) {
+ tracker.addChange({
+ op: 'remove',
+ path: path + '/' + lastIndex,
+ });
+ }
+ return Array.prototype.pop.apply(target);
+ };
+ }
+
+ if (property === 'splice') {
+ return function(start: number, deleteCount?: number, ...items: any[]) {
+ const oldLength = target.length;
+
+ // For simplicity, track splice as individual operations
+ // Remove elements
+ if (deleteCount && deleteCount > 0) {
+ for (let i = 0; i < deleteCount; i++) {
+ tracker.addChange({
+ op: 'remove',
+ path: path + '/' + (start + i),
+ });
+ }
+ }
+
+ // Add new elements
+ if (items.length > 0) {
+ items.forEach((item, i) => {
+ const insertIndex = start + i;
+ tracker.addChange({
+ op: 'add',
+ path: path + '/' + insertIndex,
+ value: item,
+ });
+ });
+ }
+
+ const result = Array.prototype.splice.apply(target, [start, deleteCount || 0, ...items]);
+
+ // Re-proxy all items that are objects
+ for (let i = 0; i < target.length; i++) {
+ if (target[i] !== null && typeof target[i] === 'object') {
+ const itemPath = path + '/' + i;
+ target[i] = tracker.createProxy(target[i], itemPath);
+ }
+ }
+
+ return result;
+ };
+ }
+
+ return Reflect.get(target, property, receiver);
+ },
+ });
+ }
+}
+
+/**
+ * JSON Patch change tracker that generates standard JSON Patch operations.
+ */
+export class JSONPatchChangeTracker extends BaseJSONPatchChangeTracker {
+ protected handleArrayElementChange(
+ target: any[],
+ index: number,
+ newValue: any,
+ propPath: string
+ ): void {
+ const isNewIndex = index >= target.length;
+
+ if (isNewIndex) {
+ // Add operation for new array elements
+ this.addChange({
+ op: 'add',
+ path: propPath.replace(/\/\d+$/, '/-'),
+ value: newValue,
+ });
+ } else {
+ // Standard replace operation for existing elements
+ this.addChange({
+ op: 'replace',
+ path: propPath,
+ value: newValue,
+ });
+ }
+ }
+
+ protected handleObjectPropertyChange(
+ oldValue: any,
+ newValue: any,
+ propPath: string
+ ): void {
+ if (oldValue === undefined) {
+ // Add operation
+ this.addChange({
+ op: 'add',
+ path: propPath,
+ value: newValue,
+ });
+ } else {
+ // Standard replace operation only (no append optimization)
+ this.addChange({
+ op: 'replace',
+ path: propPath,
+ value: newValue,
+ });
+ }
+ }
+}
+
+/**
+ * Enhanced JSON Patch change tracker with efficient string append operations.
+ */
+export class EfficientJSONPatchChangeTracker extends BaseJSONPatchChangeTracker {
+ protected handleArrayElementChange(
+ target: any[],
+ index: number,
+ newValue: any,
+ propPath: string
+ ): void {
+ const isNewIndex = index >= target.length;
+ const oldValue = isNewIndex ? undefined : target[index];
+
+ if (isNewIndex) {
+ // Add operation for new array elements
+ this.addChange({
+ op: 'add',
+ path: propPath.replace(/\/\d+$/, '/-'),
+ value: newValue,
+ });
+ } else {
+ // Check for string append operation on existing elements
+ if (typeof oldValue === 'string' && typeof newValue === 'string' && newValue.startsWith(oldValue)) {
+ const appendedPart = newValue.slice(oldValue.length);
+ if (appendedPart.length > 0) {
+ this.addChange({
+ op: 'append',
+ path: propPath,
+ value: appendedPart,
+ });
+ }
+ } else {
+ // Replace operation
+ this.addChange({
+ op: 'replace',
+ path: propPath,
+ value: newValue,
+ });
+ }
+ }
+ }
+
+ protected handleObjectPropertyChange(
+ oldValue: any,
+ newValue: any,
+ propPath: string
+ ): void {
+ // Check for string append operation
+ if (typeof oldValue === 'string' && typeof newValue === 'string' && newValue.startsWith(oldValue)) {
+ const appendedPart = newValue.slice(oldValue.length);
+ if (appendedPart.length > 0) {
+ this.addChange({
+ op: 'append',
+ path: propPath,
+ value: appendedPart,
+ });
+ }
+ } else if (oldValue === undefined) {
+ // Add operation
+ this.addChange({
+ op: 'add',
+ path: propPath,
+ value: newValue,
+ });
+ } else {
+ // Replace operation
+ this.addChange({
+ op: 'replace',
+ path: propPath,
+ value: newValue,
+ });
+ }
+ }
+}
diff --git a/ts/src/tracker/index.ts b/ts/src/tracker/index.ts
new file mode 100644
index 0000000..7edadd5
--- /dev/null
+++ b/ts/src/tracker/index.ts
@@ -0,0 +1,18 @@
+export * from './change-tracker';
+export * from './changes';
+export * from './impl';
+
+import { ChangeTracker, DiffBuffer } from './change-tracker';
+import { EfficientJSONPatchChangeTracker } from './impl';
+
+/**
+ * Track changes to an object and return a tracked proxy and diff buffer.
+ */
+export function trackChange(
+ obj: T,
+ trackerClass: new () => ChangeTracker = EfficientJSONPatchChangeTracker
+): [T, DiffBuffer] {
+ const tracker = new trackerClass();
+ const trackedObj = tracker.track(obj);
+ return [trackedObj, tracker];
+}
diff --git a/ts/tests/advanced-features.test.ts b/ts/tests/advanced-features.test.ts
new file mode 100644
index 0000000..5632b60
--- /dev/null
+++ b/ts/tests/advanced-features.test.ts
@@ -0,0 +1,341 @@
+import { z } from 'zod';
+import {
+ StreamingObject,
+ StreamingString,
+ Atom,
+ Parser,
+} from '../src/index';
+import * as ld from '../src/index';
+
+describe('Advanced Features Tests', () => {
+
+ describe('ZodType integration', () => {
+ const UuidSchema = z.string().uuid();
+
+ const ItemWithZod = ld.object({
+ id: ld.string().withZodSchema(UuidSchema),
+ name: ld.string(),
+ });
+
+ test('zod type hints work with streaming', () => {
+ const item = ItemWithZod.create();
+ const events: any[] = [];
+
+ item.id.onComplete((id: string | null) => {
+ events.push(['id_complete', id]);
+ });
+
+ item.name.onComplete((name: string | null) => {
+ events.push(['name_complete', name]);
+ });
+
+ item.update({
+ id: "123e4567-e89b-12d3-a456-426614174000",
+ name: "Test Item"
+ });
+ item.complete();
+
+ expect(events).toEqual([
+ ['id_complete', '123e4567-e89b-12d3-a456-426614174000'],
+ ['name_complete', 'Test Item'],
+ ]);
+
+ // Test Zod schema generation
+ const schema = ItemWithZod.toZod();
+ expect(() => schema.parse({
+ id: "123e4567-e89b-12d3-a456-426614174000",
+ name: "Test Item"
+ })).not.toThrow();
+
+ expect(() => schema.parse({
+ id: "not-a-uuid",
+ name: "Test Item"
+ })).toThrow();
+ });
+
+ test('multiple zod annotations', () => {
+ const ItemWithMultipleZod = ld.object({
+ id: ld.string().withZodSchema(z.string().uuid()),
+ score: ld.number().withZodSchema(z.number().min(0).max(100)),
+ });
+
+ const schema = ItemWithMultipleZod.toZod();
+
+ // Valid case
+ expect(() => schema.parse({
+ id: "123e4567-e89b-12d3-a456-426614174000",
+ score: 85
+ })).not.toThrow();
+
+ // Invalid UUID
+ expect(() => schema.parse({
+ id: "not-a-uuid",
+ score: 85
+ })).toThrow();
+
+ // Invalid score (out of range)
+ expect(() => schema.parse({
+ id: "123e4567-e89b-12d3-a456-426614174000",
+ score: 150
+ })).toThrow();
+ });
+ });
+
+ describe('Parser advanced features', () => {
+ const TestObject = ld.object({
+ message: ld.string(),
+ })
+
+ test('parser context manager behavior with use()', () => {
+ const obj = TestObject.create();
+ const parser = new Parser(obj);
+ const events: any[] = [];
+
+ obj.message.onComplete((msg: string | null) => {
+ events.push(['complete', msg]);
+ });
+
+ parser.use((p) => {
+ p.push('{"mess');
+ p.push('age": "Hel');
+ p.push('lo"}');
+ });
+
+ expect(events).toContainEqual(['complete', 'Hello']);
+ });
+
+ test('parser prevents double completion', () => {
+ const obj = TestObject.create();
+ const parser = new Parser(obj);
+ const events: any[] = [];
+
+ obj.message.onComplete((msg: string | null) => {
+ events.push(['complete', msg]);
+ });
+
+ parser.push('{"message": "Hello"}');
+ parser.complete();
+ parser.complete(); // Should not trigger again
+
+ expect(events).toHaveLength(1);
+ expect(events[0]).toEqual(['complete', 'Hello']);
+ });
+
+ test('parser exception handling in use()', () => {
+ const obj = TestObject.create();
+ const parser = new Parser(obj);
+ const events: any[] = [];
+
+ obj.message.onComplete((msg: string | null) => {
+ events.push(['complete', msg]);
+ });
+
+ expect(() => {
+ parser.use((p) => {
+ p.push('{"message": "test"}');
+ throw new Error('Test error');
+ });
+ }).toThrow('Test error');
+
+ // Should still complete despite the error
+ expect(events).toContainEqual(['complete', 'test']);
+ });
+ });
+
+ describe('Field metadata support', () => {
+ const ItemWithField = ld.object({
+ title: ld.string().describe('Item title'),
+ count: ld.number().describe('Item count').default(0),
+ });
+
+ test('should handle Field metadata', () => {
+ const item = ItemWithField.create();
+
+ // Test that object can be created and used normally
+ const events: any[] = [];
+ item.title.onComplete((title: string | null) => {
+ events.push(['title', title]);
+ });
+
+ item.count.onComplete((count: number | null) => {
+ events.push(['count', count]);
+ });
+
+ item.update({ title: 'Test', count: 5 });
+ item.complete();
+
+ expect(events).toContainEqual(['title', 'Test']);
+ expect(events).toContainEqual(['count', 5]);
+ });
+ });
+
+ describe('Complex nested structures', () => {
+ const NestedObject = ld.object({
+ nestedValue: ld.string(),
+ });
+
+ const ComplexObject = ld.object({
+ id: ld.string(),
+ nested: NestedObject,
+ items: ld.array(ld.string()),
+ metadata: ld.atom(z.any()),
+ });
+
+ test('complex nested object streaming', () => {
+ const obj = ComplexObject.create();
+ const events: any[] = [];
+
+ obj.id.onComplete((id) => events.push(['id', id]));
+ obj.nested.nestedValue.onComplete((value) => events.push(['nested_value', value]));
+ obj.items.onAppend((item, index) => {
+ events.push(['item_append', index]);
+ item.onComplete((value) => events.push(['item_complete', index, value]));
+ });
+ obj.metadata.onComplete((meta) => events.push(['metadata', meta]));
+
+ const testData = {
+ id: "test-id",
+ nested: { nestedValue: "nested-value" },
+ items: ["item1", "item2"],
+ metadata: { key: "value" }
+ };
+
+ obj.update(testData);
+ obj.complete();
+
+ expect(events).toContainEqual(['id', 'test-id']);
+ expect(events).toContainEqual(['nested_value', 'nested-value']);
+ expect(events).toContainEqual(['item_append', 0]);
+ expect(events).toContainEqual(['item_complete', 0, 'item1']);
+ expect(events).toContainEqual(['item_append', 1]);
+ expect(events).toContainEqual(['item_complete', 1, 'item2']);
+ expect(events).toContainEqual(['metadata', { key: "value" }]);
+ });
+
+ test('nested object schema generation', () => {
+ const schema = ComplexObject.toZod();
+
+ const validData = {
+ id: "test-id",
+ nested: { nestedValue: "nested-value" },
+ items: ["item1", "item2"],
+ metadata: { key: "value" }
+ };
+
+ expect(() => schema.parse(validData)).not.toThrow();
+ });
+ });
+
+ describe('Edge cases and error handling', () => {
+ const EdgeCaseObject = ld.object({
+ text: ld.string(),
+ numbers: ld.array(ld.number()),
+ });
+
+ test('empty string handling', () => {
+ const obj = EdgeCaseObject.create();
+ const events: any[] = [];
+
+ obj.text.onAppend((chunk) => events.push(['append', chunk]));
+ obj.text.onComplete((text) => events.push(['complete', text]));
+
+ obj.update({ text: "" });
+ obj.complete();
+
+ expect(events).toContainEqual(['append', '']);
+ expect(events).toContainEqual(['complete', '']);
+ });
+
+ test('null array handling', () => {
+ const obj = EdgeCaseObject.create();
+ const events: any[] = [];
+
+ obj.numbers.onAppend((item, index) => events.push(['append', item, index]));
+ obj.numbers.onComplete((items) => events.push(['complete', items]));
+
+ obj.update({ numbers: null as any });
+ obj.complete();
+
+ // The null array should be treated as empty array and complete should be called
+ const completeEvents = events.filter(e => e[0] === 'complete');
+ expect(completeEvents.length).toBeGreaterThan(0);
+ if (completeEvents.length > 0) {
+ expect(Array.isArray(completeEvents[0][1])).toBe(true);
+ expect(completeEvents[0][1]).toEqual([]);
+ }
+ });
+
+ test('partial json edge cases', () => {
+ const obj = EdgeCaseObject.create();
+ const parser = new Parser(obj);
+ const events: any[] = [];
+
+ obj.text.onAppend((chunk) => events.push(['append', chunk]));
+
+ // Test various edge cases
+ parser.push('{"text": "test\\u00'); // Incomplete unicode
+ parser.push('41"}'); // Complete it
+ parser.complete();
+
+ expect(events.length).toBeGreaterThan(0);
+ });
+
+ test('invalid streaming string update', () => {
+ const str = new StreamingString();
+ str.update('hello');
+
+ expect(() => {
+ str.update('world'); // Should fail - not a continuation
+ }).toThrow('StreamingString can only be updated with a continuation of the current value');
+ });
+ });
+
+ describe('Performance and memory tests', () => {
+ test('large list streaming performance', () => {
+ const LargeListObject = ld.object({
+ items: ld.array(ld.number()),
+ });
+
+ const obj = LargeListObject.create();
+ const events: number[] = [];
+
+ obj.items.onAppend((item: number) => {
+ events.push(item);
+ });
+
+ // Create large array
+ const largeArray = Array.from({ length: 1000 }, (_, i) => i);
+
+ const startTime = Date.now();
+ obj.update({ items: largeArray });
+ obj.complete();
+ const endTime = Date.now();
+
+ expect(events.length).toBe(1000);
+ expect(endTime - startTime).toBeLessThan(100); // Should complete quickly
+ });
+
+ test('deep nesting performance', () => {
+ const createDeepObject = (depth: number): any => {
+ if (depth === 0) return { value: "deep" };
+ return { nested: createDeepObject(depth - 1) };
+ };
+
+ const DeepObject = ld.object({
+ data: ld.atom(z.any())
+ });
+
+ const obj = DeepObject.create();
+ const events: any[] = [];
+
+ obj.data.onComplete((data) => events.push(data));
+
+ const deepData = createDeepObject(10);
+ obj.update({ data: deepData });
+ obj.complete();
+
+ expect(events).toHaveLength(1);
+ expect(events[0]).toEqual(deepData);
+ });
+ });
+});
diff --git a/ts/tests/callback-features.test.ts b/ts/tests/callback-features.test.ts
new file mode 100644
index 0000000..a309e44
--- /dev/null
+++ b/ts/tests/callback-features.test.ts
@@ -0,0 +1,277 @@
+import { z } from 'zod';
+import {
+ StreamingObject,
+ StreamingList,
+ StreamingString,
+ Atom,
+ Parser,
+ string
+} from '../src/index';
+import * as ld from '../src/index';
+
+describe('Callback Features Tests', () => {
+
+ describe('on_start callbacks', () => {
+ test('StreamingString onStart callback', () => {
+ const str = new StreamingString();
+ const events: string[] = [];
+
+ str.onStart(() => events.push('started'));
+ str.onAppend((chunk) => events.push(`append:${chunk}`));
+ str.onComplete((value) => events.push(`complete:${value}`));
+
+ str.update('hello');
+ str.update('hello world');
+ str.complete();
+
+ expect(events).toEqual([
+ 'started',
+ 'append:hello',
+ 'append: world',
+ 'complete:hello world'
+ ]);
+ });
+
+ test('StreamingList onStart callback', () => {
+ const list = new StreamingList(string());
+ const events: string[] = [];
+
+ list.onStart(() => events.push('started'));
+ list.onAppend((item, index) => events.push(`append:${index}:${item}`));
+ list.onComplete((items) => events.push(`complete:${items.length}`));
+
+ list.update(['item1']);
+ list.update(['item1', 'item2']);
+ list.complete();
+
+ expect(events[0]).toContain('started');
+ });
+
+ test('Atom onStart callback', () => {
+ const atom = new Atom(z.number());
+ const events: string[] = [];
+
+ atom.onStart(() => events.push('started'));
+ atom.onComplete((value) => events.push(`complete:${value}`));
+
+ atom.update(42);
+ atom.complete();
+
+ expect(events).toEqual([
+ 'started',
+ 'complete:42'
+ ]);
+ });
+
+ test('StreamingObject onStart callback', () => {
+ const TestObject = ld.object({
+ message: ld.string()
+ });
+
+ const obj = TestObject.create();
+ const events: string[] = [];
+
+ obj.onStart(() => events.push('object_started'));
+ obj.message.onStart(() => events.push('message_started'));
+ obj.message.onAppend((chunk) => events.push(`append:${chunk}`));
+ obj.onUpdate((value) => events.push(`update:${JSON.stringify(value)}`));
+ obj.onComplete((value) => events.push(`complete:${JSON.stringify(value)}`));
+
+ obj.update({ message: 'hello' });
+ obj.complete();
+
+ expect(events).toContain('object_started');
+ expect(events).toContain('message_started');
+ expect(events).toContain('append:hello');
+ });
+ });
+
+ describe('Complex callback scenarios', () => {
+ const Article = ld.object({
+ title: ld.string(),
+ tags: ld.array(ld.string()),
+ content: ld.string(),
+ metadata: ld.atom(z.any())
+ });
+
+ test('complex streaming scenario with all callback types', () => {
+ const article = Article.create();
+ const events: any[] = [];
+
+ // Set up comprehensive event tracking
+ article.onStart(() => events.push(['article_start']));
+ article.onUpdate((data) => events.push(['article_update', Object.keys(data)]));
+ article.onComplete((data) => events.push(['article_complete', Object.keys(data)]));
+
+ article.title.onStart(() => events.push(['title_start']));
+ article.title.onAppend((chunk) => events.push(['title_append', chunk]));
+ article.title.onComplete((value) => events.push(['title_complete', value]));
+
+ article.tags.onStart(() => events.push(['tags_start']));
+ article.tags.onAppend((tag, index) => {
+ events.push(['tags_append', index]);
+ tag.onStart(() => events.push(['tag_start', index]));
+ tag.onAppend((chunk) => events.push(['tag_append', index, chunk]));
+ tag.onComplete((value) => events.push(['tag_complete', index, value]));
+ });
+ article.tags.onComplete((tags) => events.push(['tags_complete', tags.length]));
+
+ article.content.onStart(() => events.push(['content_start']));
+ article.content.onAppend((chunk) => events.push(['content_append', chunk]));
+ article.content.onComplete((value) => events.push(['content_complete', value?.slice(0, 10)]));
+
+ article.metadata.onStart(() => events.push(['metadata_start']));
+ article.metadata.onComplete((value) => events.push(['metadata_complete', value]));
+
+ // Simulate progressive streaming
+ article.update({ title: 'My Art' });
+ article.update({
+ title: 'My Article',
+ tags: ['tech']
+ });
+ article.update({
+ title: 'My Article',
+ tags: ['tech', 'programming'],
+ content: 'This is the beginning'
+ });
+ article.update({
+ title: 'My Article',
+ tags: ['tech', 'programming'],
+ content: 'This is the beginning of a great article about technology.',
+ metadata: { author: 'John', date: '2024-01-01' }
+ });
+ article.complete();
+
+ // Verify key events occurred
+ expect(events).toContainEqual(['article_start']);
+ expect(events).toContainEqual(['title_start']);
+ expect(events).toContainEqual(['title_append', 'My Art']);
+
+ // Title might complete as 'My Art' or 'My Article' depending on when complete() is called
+ const titleComplete = events.find(e => e[0] === 'title_complete');
+ expect(titleComplete).toBeDefined();
+ expect(typeof titleComplete[1]).toBe('string');
+
+ expect(events).toContainEqual(['tags_start']);
+ expect(events).toContainEqual(['tags_append', 0]);
+ expect(events).toContainEqual(['tag_complete', 0, 'tech']);
+ expect(events).toContainEqual(['content_start']);
+ expect(events).toContainEqual(['metadata_complete', { author: 'John', date: '2024-01-01' }]);
+ });
+
+ test('streaming with parser integration', () => {
+ const article = Article.create();
+ const parser = new Parser(article);
+ const events: any[] = [];
+
+ article.title.onStart(() => events.push('title_started'));
+ article.title.onAppend((chunk) => events.push(`title_chunk:${chunk}`));
+ article.title.onComplete((value) => events.push(`title_done:${value}`));
+
+ const jsonStr = JSON.stringify({
+ title: 'Streaming Article',
+ tags: ['streaming', 'json'],
+ content: 'Content here',
+ metadata: { version: 1 }
+ });
+
+ // Parse character by character to test progressive streaming
+ for (let i = 0; i < jsonStr.length; i++) {
+ parser.push(jsonStr[i]);
+ }
+ parser.complete();
+
+ expect(events).toContain('title_started');
+ expect(events).toContain('title_done:Streaming Article');
+
+ // Should have received title in chunks
+ const titleChunks = events.filter(e => typeof e === 'string' && e.startsWith('title_chunk:'));
+ expect(titleChunks.length).toBeGreaterThan(1);
+ });
+ });
+
+ describe('Callback error handling', () => {
+ test('should throw callback errors naturally', () => {
+ const str = new StreamingString();
+
+ str.onStart(() => {
+ throw new Error('Start callback error');
+ });
+
+ // Should throw the callback error
+ expect(() => {
+ str.update('test');
+ }).toThrow('Start callback error');
+ });
+
+ test('should allow users to handle errors in their callbacks', () => {
+ const str = new StreamingString();
+ const events: string[] = [];
+
+ str.onStart(() => {
+ try {
+ throw new Error('Start callback error');
+ } catch (error) {
+ events.push('error-handled');
+ }
+ });
+
+ str.onAppend((chunk) => events.push(`append:${chunk}`));
+
+ // Should not throw when user handles error
+ expect(() => {
+ str.update('test');
+ }).not.toThrow();
+
+ expect(events).toContain('error-handled');
+ expect(events).toContain('append:test');
+ });
+
+ test('multiple callback registration', () => {
+ const str = new StreamingString();
+ const events: string[] = [];
+
+ str.onAppend((chunk) => events.push(`first:${chunk}`));
+ str.onAppend((chunk) => events.push(`second:${chunk}`));
+ str.onComplete((value) => events.push(`first_complete:${value}`));
+ str.onComplete((value) => events.push(`second_complete:${value}`));
+
+ str.update('test');
+ str.complete();
+
+ expect(events).toContain('first:test');
+ expect(events).toContain('second:test');
+ expect(events).toContain('first_complete:test');
+ expect(events).toContain('second_complete:test');
+ });
+ });
+
+ describe('Callback timing and order', () => {
+ test('callback execution order is correct', () => {
+ const OrderTest = ld.object({
+ field1: ld.string(),
+ field2: ld.string()
+ });
+
+ const obj = OrderTest.create();
+ const events: any[] = [];
+
+ obj.onStart(() => events.push(['object_start']));
+ obj.field1.onStart(() => events.push(['field1_start']));
+ obj.field1.onComplete((v) => events.push(['field1_complete', v]));
+ obj.field2.onStart(() => events.push(['field2_start']));
+ obj.field2.onComplete((v) => events.push(['field2_complete', v]));
+ obj.onComplete((v) => events.push(['object_complete']));
+
+ obj.update({ field1: 'value1' });
+ obj.update({ field1: 'value1', field2: 'value2' });
+ obj.complete();
+
+ // Verify the order: object starts first, then fields in order
+ const startEvents = events.filter(e => e[0].includes('_start'));
+ expect(startEvents[0]).toEqual(['object_start']);
+ expect(startEvents[1]).toEqual(['field1_start']);
+ expect(startEvents[2]).toEqual(['field2_start']);
+ });
+ });
+});
\ No newline at end of file
diff --git a/ts/tests/change-tracker.test.ts b/ts/tests/change-tracker.test.ts
new file mode 100644
index 0000000..d38ef27
--- /dev/null
+++ b/ts/tests/change-tracker.test.ts
@@ -0,0 +1,440 @@
+import { trackChange, applyChange, Operation, JSONPatchChangeTracker, EfficientJSONPatchChangeTracker } from '../src/tracker';
+
+// Define TypeScript interfaces for complex nested structures
+interface Author {
+ name: string;
+ email: string | null;
+}
+
+interface Article {
+ title: string;
+ tags: string[];
+ author: Author;
+ contributors: Author[];
+ metadata: { [key: string]: string };
+}
+
+describe('Change Tracking', () => {
+ test('should track object property changes', () => {
+ const [tracked, diffBuf] = trackChange({ name: 'John', age: 30 });
+
+ tracked.name = 'Jane';
+ const changes = diffBuf.flush();
+
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'replace',
+ path: '/name',
+ value: 'Jane',
+ });
+ });
+
+ test('should track array additions', () => {
+ const [tracked, diffBuf] = trackChange({ items: ['a', 'b'] });
+
+ tracked.items.push('c');
+ const changes = diffBuf.flush();
+
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'add',
+ path: '/items/-',
+ value: 'c',
+ });
+ });
+
+ test('should track string append operations', () => {
+ const [tracked, diffBuf] = trackChange({ message: 'Hello' });
+
+ tracked.message = 'Hello World';
+ const changes = diffBuf.flush();
+
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'append',
+ path: '/message',
+ value: ' World',
+ });
+ });
+
+ test('should apply changes correctly', () => {
+ const obj = { message: 'Hello', count: 0 };
+ const operations: Operation[] = [
+ { op: 'append', path: '/message', value: ' World' },
+ { op: 'replace', path: '/count', value: 5 },
+ ];
+
+ applyChange(obj, operations);
+
+ expect(obj.message).toBe('Hello World');
+ expect(obj.count).toBe(5);
+ });
+
+ test('complex nested object change tracking', () => {
+ const article: Article = {
+ title: 'Initial Title',
+ tags: ['python', 'testing'],
+ author: { name: 'Alice', email: 'alice@example.com' },
+ contributors: [{ name: 'Bob', email: 'bob@example.com' }],
+ metadata: {},
+ };
+
+ const [tracked, tracker] = trackChange(article);
+
+ // Test title change
+ tracked.title = 'Updated Title';
+ let changes = tracker.flush();
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'replace',
+ path: '/title',
+ value: 'Updated Title',
+ });
+
+ // Test nested object property append (string concatenation)
+ tracked.author.name = 'Alice Smith';
+ changes = tracker.flush();
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'append',
+ path: '/author/name',
+ value: ' Smith',
+ });
+
+ // Test array append
+ tracked.tags.push('diff');
+ changes = tracker.flush();
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'add',
+ path: '/tags/-',
+ value: 'diff',
+ });
+
+ // Test adding new object to array
+ tracked.contributors.push({ name: 'Charlie', email: 'charlie@example.com' });
+ changes = tracker.flush();
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'add',
+ path: '/contributors/-',
+ value: { name: 'Charlie', email: 'charlie@example.com' },
+ });
+
+ // Test setting nested property to null
+ tracked.author.email = null;
+ changes = tracker.flush();
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'replace',
+ path: '/author/email',
+ value: null,
+ });
+
+ // Test setting nested property back from null
+ tracked.author.email = 'alice@example.com';
+ changes = tracker.flush();
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'replace',
+ path: '/author/email',
+ value: 'alice@example.com',
+ });
+
+ // Test adding property to object/map
+ tracked.metadata['version'] = '1.0';
+ changes = tracker.flush();
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'add',
+ path: '/metadata/version',
+ value: '1.0',
+ });
+
+ // Test removing property from object/map
+ delete tracked.metadata['version'];
+ changes = tracker.flush();
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'remove',
+ path: '/metadata/version',
+ });
+ });
+
+ test('should track multiple simultaneous changes', () => {
+ const obj = {
+ user: { name: 'John', age: 30 },
+ items: ['a', 'b'],
+ settings: { theme: 'dark' },
+ };
+
+ const [tracked, tracker] = trackChange(obj);
+
+ // Make multiple changes before flushing
+ tracked.user.name = 'John Doe';
+ tracked.items.push('c');
+ tracked.settings.theme = 'light';
+
+ const changes = tracker.flush();
+ expect(changes).toHaveLength(3);
+
+ expect(changes).toContainEqual({
+ op: 'append',
+ path: '/user/name',
+ value: ' Doe',
+ });
+ expect(changes).toContainEqual({
+ op: 'add',
+ path: '/items/-',
+ value: 'c',
+ });
+ expect(changes).toContainEqual({
+ op: 'replace',
+ path: '/settings/theme',
+ value: 'light',
+ });
+ });
+
+ test('should handle basic array modifications', () => {
+ const obj = { items: ['a', 'b', 'c'] };
+ const [tracked, tracker] = trackChange(obj);
+
+ // Test array push
+ tracked.items.push('d');
+ let changes = tracker.flush();
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'add',
+ path: '/items/-',
+ value: 'd',
+ });
+
+ // Test direct index assignment
+ tracked.items[0] = 'A';
+ changes = tracker.flush();
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'replace',
+ path: '/items/0',
+ value: 'A',
+ });
+ });
+
+ test('nested array object property modification', () => {
+ const article: Article = {
+ title: 'Title',
+ tags: ['tag1'],
+ author: { name: 'Alice', email: 'alice@example.com' },
+ contributors: [{ name: 'Bob', email: 'bob@example.com' }],
+ metadata: {},
+ };
+
+ const [tracked, tracker] = trackChange(article);
+
+ // Test nested array object property modification with string append
+ tracked.contributors[0].name = 'Bob Smith';
+
+ let changes = tracker.flush();
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'append',
+ path: '/contributors/0/name',
+ value: ' Smith',
+ });
+
+ // Test nested array object property replacement
+ tracked.contributors[0].email = 'newbob@example.com';
+
+ changes = tracker.flush();
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'replace',
+ path: '/contributors/0/email',
+ value: 'newbob@example.com',
+ });
+ });
+
+ test('array pop operation', () => {
+ const obj = { items: ['a', 'b', 'c'] };
+ const [tracked, tracker] = trackChange(obj);
+
+ tracked.items.pop();
+ const changes = tracker.flush();
+
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'remove',
+ path: '/items/2',
+ });
+ });
+
+ test('array splice operations', () => {
+ const obj = { items: [1, 2, 3, 4, 5] };
+ const [tracked, tracker] = trackChange(obj);
+
+ // Remove one element from middle
+ tracked.items.splice(2, 1);
+ let changes = tracker.flush();
+
+ expect(changes).toHaveLength(1);
+ expect(changes[0]).toEqual({
+ op: 'remove',
+ path: '/items/2',
+ });
+
+ // Insert elements
+ tracked.items.splice(1, 0, 10, 20);
+ changes = tracker.flush();
+
+ expect(changes).toHaveLength(2);
+ expect(changes[0]).toEqual({
+ op: 'add',
+ path: '/items/1',
+ value: 10,
+ });
+ expect(changes[1]).toEqual({
+ op: 'add',
+ path: '/items/2',
+ value: 20,
+ });
+ });
+
+ describe('Direct ChangeTracker Class Comparison', () => {
+ test('JSONPatchChangeTracker vs EfficientJSONPatchChangeTracker - object string append', () => {
+ const initialObj = { message: 'Hello' };
+
+ // Test JSONPatchChangeTracker
+ const standardTracker = new JSONPatchChangeTracker();
+ const standardTracked = standardTracker.track({ ...initialObj });
+ standardTracked.message = 'Hello World';
+ const standardChanges = standardTracker.flush();
+
+ // Test EfficientJSONPatchChangeTracker
+ const efficientTracker = new EfficientJSONPatchChangeTracker();
+ const efficientTracked = efficientTracker.track({ ...initialObj });
+ efficientTracked.message = 'Hello World';
+ const efficientChanges = efficientTracker.flush();
+
+ // Standard should use replace, Efficient should use append
+ expect(standardChanges).toEqual([{
+ op: 'replace',
+ path: '/message',
+ value: 'Hello World',
+ }]);
+
+ expect(efficientChanges).toEqual([{
+ op: 'append',
+ path: '/message',
+ value: ' World',
+ }]);
+ });
+
+ test('JSONPatchChangeTracker vs EfficientJSONPatchChangeTracker - array element string append', () => {
+ const initialObj = { items: ['Hello'] };
+
+ // Test JSONPatchChangeTracker
+ const standardTracker = new JSONPatchChangeTracker();
+ const standardTracked = standardTracker.track({ items: [...initialObj.items] });
+ standardTracked.items[0] = 'Hello World';
+ const standardChanges = standardTracker.flush();
+
+ // Test EfficientJSONPatchChangeTracker
+ const efficientTracker = new EfficientJSONPatchChangeTracker();
+ const efficientTracked = efficientTracker.track({ items: [...initialObj.items] });
+ efficientTracked.items[0] = 'Hello World';
+ const efficientChanges = efficientTracker.flush();
+
+ // Standard should use replace, Efficient should use append
+ expect(standardChanges).toEqual([{
+ op: 'replace',
+ path: '/items/0',
+ value: 'Hello World',
+ }]);
+
+ expect(efficientChanges).toEqual([{
+ op: 'append',
+ path: '/items/0',
+ value: ' World',
+ }]);
+ });
+
+ test('Both trackers should handle non-string values identically', () => {
+ const initialObj = { count: 5, items: [10] };
+
+ // Test JSONPatchChangeTracker
+ const standardTracker = new JSONPatchChangeTracker();
+ const standardTracked = standardTracker.track({ ...initialObj, items: [...initialObj.items] });
+ standardTracked.count = 10;
+ standardTracked.items[0] = 20;
+ const standardChanges = standardTracker.flush();
+
+ // Test EfficientJSONPatchChangeTracker
+ const efficientTracker = new EfficientJSONPatchChangeTracker();
+ const efficientTracked = efficientTracker.track({ ...initialObj, items: [...initialObj.items] });
+ efficientTracked.count = 10;
+ efficientTracked.items[0] = 20;
+ const efficientChanges = efficientTracker.flush();
+
+ // Both should produce identical results for non-string values
+ expect(standardChanges).toEqual(efficientChanges);
+ expect(standardChanges).toEqual([
+ { op: 'replace', path: '/count', value: 10 },
+ { op: 'replace', path: '/items/0', value: 20 },
+ ]);
+ });
+ });
+
+ describe('Edge Cases', () => {
+ test('should handle empty string append correctly', () => {
+ const [tracked, diffBuf] = trackChange({ message: 'Hello' });
+
+ // Appending empty string should not generate changes
+ tracked.message = 'Hello';
+ const changes = diffBuf.flush();
+
+ expect(changes).toHaveLength(0);
+ });
+
+ test('should handle non-append string changes', () => {
+ const [tracked, diffBuf] = trackChange({ message: 'Hello World' });
+
+ // String that doesn't start with original should be replace
+ tracked.message = 'Goodbye World';
+ const changes = diffBuf.flush();
+
+ expect(changes).toEqual([{
+ op: 'replace',
+ path: '/message',
+ value: 'Goodbye World',
+ }]);
+ });
+
+ test('should handle empty array operations', () => {
+ const [tracked, diffBuf] = trackChange({ items: [] as string[] });
+
+ // Adding to empty array
+ tracked.items.push('first');
+ const changes = diffBuf.flush();
+
+ expect(changes).toEqual([{
+ op: 'add',
+ path: '/items/-',
+ value: 'first',
+ }]);
+ });
+
+ test('should handle array bounds edge cases', () => {
+ const [tracked, diffBuf] = trackChange({ items: ['a', 'b'] });
+
+ // Direct assignment beyond array length
+ tracked.items[5] = 'f';
+ const changes = diffBuf.flush();
+
+ expect(changes).toEqual([{
+ op: 'add',
+ path: '/items/-',
+ value: 'f',
+ }]);
+ });
+ });
+});
diff --git a/ts/tests/ld-schema.test.ts b/ts/tests/ld-schema.test.ts
new file mode 100644
index 0000000..d9e5a59
--- /dev/null
+++ b/ts/tests/ld-schema.test.ts
@@ -0,0 +1,433 @@
+/**
+ * Comprehensive tests for ld namespace (LangDiff schema functions)
+ */
+
+import z from 'zod';
+import * as ld from '../src';
+import {
+ StreamingString,
+ StreamingList,
+ Atom,
+ StreamingObject,
+ Parser
+} from '../src';
+
+describe('ld namespace', () => {
+ describe('ld.string()', () => {
+ it('should create a StreamingString instance', () => {
+ const str = ld.string().create();
+ expect(str).toBeInstanceOf(StreamingString);
+ });
+
+ it('should handle string streaming correctly', () => {
+ const str = ld.string().create();
+ const chunks: string[] = [];
+
+ str.onAppend((chunk) => {
+ chunks.push(chunk);
+ });
+
+ str.update('Hello');
+ str.update('Hello World');
+ str.complete();
+
+ expect(chunks).toEqual(['Hello', ' World']);
+ expect(str.value).toBe('Hello World');
+ });
+
+ it('should trigger onComplete callback', () => {
+ const str = ld.string().create();
+ let completed = false;
+ let finalValue = '';
+
+ str.onComplete((value) => {
+ completed = true;
+ finalValue = value || '';
+ });
+
+ str.update('Test');
+ str.complete();
+
+ expect(completed).toBe(true);
+ expect(finalValue).toBe('Test');
+ });
+ });
+
+ describe('ld.number()', () => {
+ it('should create a Atom for numbers', () => {
+ const num = ld.number().create();
+ expect(num).toBeInstanceOf(Atom);
+ });
+
+ it('should handle number values', () => {
+ const num = ld.number().create();
+ let finalValue: number | null = null;
+
+ num.onComplete((value) => {
+ finalValue = value;
+ });
+
+ num.update(42);
+ num.complete();
+
+ expect(finalValue).toBe(42);
+ expect(num.value).toBe(42);
+ });
+ });
+
+ describe('ld.boolean()', () => {
+ it('should create a Atom for booleans', () => {
+ const bool = ld.boolean().create();
+ expect(bool).toBeInstanceOf(Atom);
+ });
+
+ it('should handle boolean values', () => {
+ const bool = ld.boolean().create();
+ let finalValue: boolean | null = null;
+
+ bool.onComplete((value) => {
+ finalValue = value;
+ });
+
+ bool.update(true);
+ bool.complete();
+
+ expect(finalValue).toBe(true);
+ expect(bool.value).toBe(true);
+ });
+ });
+
+ describe('ld.atom()', () => {
+ it('should create a Atom instance', () => {
+ const atom = ld.atom(z.string()).create();
+ expect(atom).toBeInstanceOf(Atom);
+ });
+
+ it('should handle generic atomic values', () => {
+ const atom = ld.atom(z.string()).create();
+ let finalValue: string | null = null;
+
+ atom.onComplete((value) => {
+ finalValue = value;
+ });
+
+ atom.update('atomic value');
+ atom.complete();
+
+ expect(finalValue).toBe('atomic value');
+ expect(atom.value).toBe('atomic value');
+ });
+
+ it('should work with custom classes', () => {
+ const obj = z.object({
+ value: z.string(),
+ });
+
+ const atom = ld.atom(obj).create();
+ atom.update({ value: 'test' });
+ atom.complete();
+
+ expect(atom.value?.value).toBe('test');
+ });
+ });
+
+ describe('ld.array()', () => {
+ it('should create a StreamingList instance', () => {
+ const arr = ld.array(ld.string()).create();
+ expect(arr).toBeInstanceOf(StreamingList);
+ });
+
+ it('should handle array of strings', () => {
+ const arr = ld.array(ld.string()).create();
+ const appendedItems: Array<{ item: StreamingString; index: number }> = [];
+
+ arr.onAppend((item, index) => {
+ appendedItems.push({ item, index });
+ });
+
+ // @ts-ignore - Runtime works correctly, type system limitation
+ arr.update(['first']);
+ // @ts-ignore - Runtime works correctly, type system limitation
+ arr.update(['first', 'second']);
+ arr.complete();
+
+ expect(appendedItems).toHaveLength(2);
+ expect(appendedItems[0].index).toBe(0);
+ expect(appendedItems[1].index).toBe(1);
+ });
+
+ it('should handle streaming array items', () => {
+ const arr = ld.array(ld.string()).create();
+ const itemChunks: string[] = [];
+
+ arr.onAppend((item, index) => {
+ item.onAppend((chunk) => {
+ itemChunks.push(`${index}:${chunk}`);
+ });
+ });
+
+ // @ts-ignore - Runtime works correctly, type system limitation
+ arr.update(['Hello']);
+ // @ts-ignore - Runtime works correctly, type system limitation
+ arr.update(['Hello World']);
+ arr.complete();
+
+ expect(itemChunks).toContain('0:Hello');
+ expect(itemChunks).toContain('0: World');
+ });
+
+ it('should handle array of atoms', () => {
+ const arr = ld.array(ld.number()).create();
+ let completed = false;
+
+ arr.onComplete(() => {
+ completed = true;
+ });
+
+ // @ts-ignore - Runtime works correctly, type system limitation
+ arr.update([1, 2, 3]);
+ arr.complete();
+
+ expect(completed).toBe(true);
+ expect(arr.value).toEqual([1, 2, 3]);
+ });
+ });
+
+ describe('ld.object()', () => {
+ it('should create a StreamingObject class', () => {
+ const ObjectClass = ld.object({
+ name: ld.string(),
+ age: ld.number()
+ });
+
+ const instance = ObjectClass.create();
+ expect(instance).toBeInstanceOf(StreamingObject);
+ });
+
+ it('should provide proper field access', () => {
+ const ObjectClass = ld.object({
+ title: ld.string(),
+ items: ld.array(ld.string())
+ });
+
+ const instance = ObjectClass.create();
+
+ // Fields should be accessible
+ expect(instance.title).toBeInstanceOf(StreamingString);
+ expect(instance.items).toBeInstanceOf(StreamingList);
+ });
+
+ it('should handle object streaming correctly', () => {
+ const ObjectClass = ld.object({
+ name: ld.string(),
+ score: ld.number()
+ });
+
+ const instance = ObjectClass.create();
+ const nameChunks: string[] = [];
+ let finalScore: number | null = null;
+
+ instance.name.onAppend((chunk) => {
+ nameChunks.push(chunk);
+ });
+
+ instance.score.onComplete((value) => {
+ finalScore = value;
+ });
+
+ // Simulate streaming object data
+ instance.update({ name: 'John' });
+ instance.update({ name: 'John Doe', score: 95 });
+ instance.complete();
+
+ expect(nameChunks).toContain('John');
+ // Note: Object streaming may not show incremental changes in the same way as Parser
+ expect(finalScore).toBe(95);
+ });
+
+ it('should work with nested objects', () => {
+ const UserClass = ld.object({
+ name: ld.string(),
+ age: ld.number()
+ });
+
+ const PostClass = ld.object({
+ title: ld.string(),
+ author: UserClass
+ });
+
+ const post = PostClass.create();
+ expect(post.title).toBeInstanceOf(StreamingString);
+ // Note: Nested objects are currently implemented as class constructors
+ expect(post.author).toBeDefined();
+ expect(typeof post.author === 'function' || typeof post.author === 'object').toBe(true);
+ });
+
+ it('should handle complex nested structures', () => {
+ const ObjectClass = ld.object({
+ users: ld.array(ld.string()) // Simplified to array of strings for now
+ });
+
+ const instance = ObjectClass.create();
+ let userAdded = false;
+
+ instance.users.onAppend((user, index) => {
+ userAdded = true;
+ expect(user).toBeInstanceOf(StreamingString);
+ expect(index).toBe(0);
+ });
+
+ // @ts-ignore - Runtime works correctly, type system limitation
+ instance.update({
+ users: ['Alice']
+ });
+
+ expect(userAdded).toBe(true);
+ });
+ });
+
+ describe('Integration with Parser', () => {
+ it('should work with Parser for streaming JSON', async () => {
+ const ResponseClass = ld.object({
+ message: ld.string(),
+ items: ld.array(ld.string())
+ });
+
+ const response = ResponseClass.create();
+ const parser = new Parser(response);
+
+ const chunks: string[] = [];
+ const items: string[] = [];
+
+ response.message.onAppend((chunk) => {
+ chunks.push(chunk);
+ });
+
+ response.items.onAppend((item, index) => {
+ item.onComplete((value) => {
+ items[index] = value || '';
+ });
+ });
+
+ // Simulate streaming JSON
+ const jsonChunks = [
+ '{"message": "Hello',
+ ' World", "items": ["first',
+ '", "second"]}'
+ ];
+
+ for (const chunk of jsonChunks) {
+ parser.push(chunk);
+ }
+ parser.complete();
+
+ expect(chunks).toContain('Hello');
+ // Parser may have specific streaming behavior - check final state
+ expect(response.message.value).toBeTruthy();
+ expect(items).toEqual(['first', 'second']);
+ });
+ });
+
+ describe('Type Safety', () => {
+ it('should maintain type information through compilation', () => {
+ const ObjectClass = ld.object({
+ name: ld.string(),
+ count: ld.number(),
+ active: ld.boolean(),
+ tags: ld.array(ld.string())
+ });
+
+ const instance = ObjectClass.create();
+
+ // These should not cause TypeScript compilation errors
+ instance.name.onAppend(() => {});
+ instance.count.onComplete(() => {});
+ instance.active.update(true);
+ instance.tags.onAppend(() => {});
+
+ // Type checking at runtime
+ expect(instance.name).toBeInstanceOf(StreamingString);
+ expect(instance.count).toBeInstanceOf(Atom);
+ expect(instance.active).toBeInstanceOf(Atom);
+ expect(instance.tags).toBeInstanceOf(StreamingList);
+ });
+ });
+
+ describe('Error Handling', () => {
+ it('should handle invalid string updates', () => {
+ const str = ld.string().create();
+ str.update('Hello');
+
+ expect(() => {
+ str.update('Different'); // Should fail - not a continuation
+ }).toThrow();
+ });
+
+ it('should handle empty arrays', () => {
+ const arr = ld.array(ld.string()).create();
+ let completedCalled = false;
+
+ arr.onComplete(() => {
+ completedCalled = true;
+ });
+
+ arr.update([]);
+ arr.complete();
+
+ expect(completedCalled).toBe(true);
+ expect(arr.value).toEqual([]);
+ });
+
+ it('should handle null values gracefully', () => {
+ const str = ld.string().create();
+
+ str.update(null);
+ str.complete();
+
+ expect(str.value).toBe(null);
+ });
+ });
+
+ describe('Performance', () => {
+ it('should handle large arrays efficiently', () => {
+ const arr = ld.array(ld.string()).create();
+ const largeArray = Array.from({ length: 1000 }, (_, i) => `item-${i}`);
+
+ const start = Date.now();
+ // @ts-ignore - Runtime works correctly, type system limitation
+ arr.update(largeArray);
+ arr.complete();
+ const end = Date.now();
+
+ expect(end - start).toBeLessThan(100); // Should complete in < 100ms
+ expect(arr.value).toHaveLength(1000);
+ });
+
+ it('should handle deep nested objects', () => {
+ const DeepObjectClass = ld.object({
+ level1: ld.object({
+ level2: ld.object({
+ level3: ld.object({
+ value: ld.string()
+ })
+ })
+ })
+ });
+
+ const instance = DeepObjectClass.create();
+
+ expect(() => {
+ instance.update({
+ level1: {
+ level2: {
+ level3: {
+ value: 'deep'
+ }
+ }
+ }
+ });
+ instance.complete();
+ }).not.toThrow();
+ });
+ });
+});
diff --git a/ts/tests/parser.test.ts b/ts/tests/parser.test.ts
new file mode 100644
index 0000000..8e8d3f0
--- /dev/null
+++ b/ts/tests/parser.test.ts
@@ -0,0 +1,507 @@
+import { Parser } from '../src/parser/parser';
+import { StreamingObject, StreamingString, StreamingList, Atom, StreamingAtomList } from '../src/parser/model';
+import { z } from 'zod';
+import * as ld from '../src';
+
+const TestObject = ld.object({
+ message: ld.string(),
+});
+type TestObject = ld.infer;
+
+const Block = ld.object({
+ id: ld.string(),
+ title: ld.string(),
+ labels: ld.array(ld.string()),
+ minutes: ld.number(),
+})
+type Block = ld.infer;
+
+const CreateBlocks = ld.object({
+ blocks: ld.array(Block),
+}).describe("CreateBlocks is a tool for creating blocks with streaming updates.");
+type CreateBlocks = ld.infer;
+
+const StreamingContainer = ld.object({
+ items: ld.array(ld.atom(z.string())),
+});
+type StreamingContainer = ld.infer;
+
+const StreamingContainerWithAtom = ld.object({
+ title: ld.atom(z.string()),
+ item: ld.atom(z.any()),
+});
+type StreamingContainerWithAtom = ld.infer;
+
+const StreamingContainerWithStringList = ld.object({
+ items: ld.array(ld.string()),
+});
+type StreamingContainerWithStringList = ld.infer;
+
+const StreamingContainerWithNullableAtom = ld.object({
+ item: ld.atom(z.string().nullable()),
+});
+type StreamingContainerWithNullableAtom = ld.infer;
+
+const StreamingContainerWithAtomList = ld.object({
+ items: ld.array(ld.atom(z.any())),
+});
+type StreamingContainerWithAtomList = ld.infer;
+
+const StreamingContainerWithString = ld.object({
+ item: ld.string(),
+});
+type StreamingContainerWithString = ld.infer;
+
+describe('Parser', () => {
+ test('should handle streaming with parser', () => {
+ const obj = TestObject.create();
+ const parser = new Parser(obj);
+ const updates: any[] = [];
+
+ obj.onUpdate((value) => {
+ updates.push({ ...value });
+ });
+
+ parser.push('{"mess');
+ parser.push('age": "Hel');
+ parser.push('lo"}');
+ parser.complete();
+
+ expect(updates.length).toBeGreaterThan(0);
+ expect(obj.getValue()).toEqual({ message: 'Hello' });
+ });
+
+ test('streaming object with complex structure', () => {
+ function installHandlers(tool: CreateBlocks, events: any[]) {
+ tool.blocks.onAppend((block: Block, index: number) => {
+ events.push(['on_block_append', index]);
+
+ block.id.onComplete((id: string | null) => {
+ events.push(['on_id_complete', index, id]);
+ });
+
+ block.title.onAppend((chunk: string) => {
+ events.push(['on_title_append', index, chunk]);
+ });
+
+ block.title.onComplete((title: string | null) => {
+ events.push(['on_title_complete', index, title]);
+ });
+
+ block.labels.onAppend((label: StreamingString, labelIndex: number) => {
+ events.push(['on_label_append', index]);
+
+ label.onComplete((labelValue: string | null) => {
+ events.push(['on_label_complete', index, labelValue]);
+ });
+ });
+
+ block.minutes.onComplete((minutes: number) => {
+ events.push(['on_minutes_complete', index, minutes]);
+ });
+
+ block.onComplete((_: any) => {
+ events.push(['on_block_complete', index]);
+ });
+ });
+
+ tool.blocks.onComplete((blocks) => {
+ events.push(['on_blocks_complete', blocks.length]);
+ });
+ }
+
+ const tool = CreateBlocks.create();
+ const events: any[] = [];
+ installHandlers(tool, events);
+
+ const fullJson = JSON.stringify({
+ blocks: [
+ {
+ id: 'block1',
+ title: 'Block One',
+ labels: ['label1', 'label2'],
+ minutes: 10,
+ score: 0.9,
+ },
+ {
+ id: 'block2',
+ title: 'Block Two',
+ labels: ['label3'],
+ minutes: 5,
+ score: 0.8,
+ },
+ ],
+ });
+
+ const parser = new Parser(tool);
+ for (let i = 0; i < fullJson.length; i++) {
+ parser.push(fullJson[i]);
+ }
+ parser.complete();
+
+ expect(events).toContainEqual(['on_block_append', 0]);
+ expect(events).toContainEqual(['on_id_complete', 0, 'block1']);
+ expect(events).toContainEqual(['on_title_complete', 0, 'Block One']);
+ expect(events).toContainEqual(['on_label_complete', 0, 'label1']);
+ expect(events).toContainEqual(['on_label_complete', 0, 'label2']);
+ expect(events).toContainEqual(['on_minutes_complete', 0, 10]);
+ expect(events).toContainEqual(['on_block_complete', 0]);
+ expect(events).toContainEqual(['on_block_append', 1]);
+ expect(events).toContainEqual(['on_id_complete', 1, 'block2']);
+ expect(events).toContainEqual(['on_title_complete', 1, 'Block Two']);
+ expect(events).toContainEqual(['on_label_complete', 1, 'label3']);
+ expect(events).toContainEqual(['on_minutes_complete', 1, 5]);
+ expect(events).toContainEqual(['on_block_complete', 1]);
+ expect(events).toContainEqual(['on_blocks_complete', 2]);
+ });
+
+ test('streaming object two keys at once', () => {
+ const block = Block.create();
+ const events: any[] = [];
+
+ block.id.onAppend((chunk: string) => {
+ events.push(['on_id_append', chunk]);
+ });
+
+ block.id.onComplete((id: string | null) => {
+ events.push(['on_id_complete', id]);
+ });
+
+ block.title.onAppend((chunk: string) => {
+ events.push(['on_title_append', chunk]);
+ });
+
+ block.update({ id: 'block1', title: 'Block One' });
+
+ expect(events).toContainEqual(['on_id_append', 'block1']);
+ expect(events).toContainEqual(['on_id_complete', 'block1']);
+ expect(events).toContainEqual(['on_title_append', 'Block One']);
+ });
+
+ test('streaming object empty string', () => {
+ const block = Block.create();
+ const events: any[] = [];
+
+ block.id.onAppend((chunk: string) => {
+ events.push(['on_id_append', chunk]);
+ });
+
+ block.id.onComplete((id: string | null) => {
+ events.push(['on_id_complete', id]);
+ });
+
+ block.update({ id: '', title: 'Block One' });
+
+ expect(events).toContainEqual(['on_id_append', '']);
+ expect(events).toContainEqual(['on_id_complete', '']);
+ });
+
+ test('streaming list complete value', () => {
+ const container = StreamingContainer.create();
+ const events: any[] = [];
+
+ container.items.onAppend((item: string, index: number) => {
+ events.push(['on_item_append', item, index]);
+ });
+
+ container.items.onComplete((items: string[]) => {
+ events.push(['on_items_complete', items]);
+ });
+
+ container.update({ items: ['item1'] });
+ expect(events).toEqual([]);
+
+ container.update({ items: ['item1', 'item2', 'item3'] });
+ expect(events).toContainEqual(['on_item_append', 'item1', 0]);
+ expect(events).toContainEqual(['on_item_append', 'item2', 1]);
+
+ events.length = 0; // Clear events
+
+ container.complete();
+ expect(events).toContainEqual(['on_item_append', 'item3', 2]);
+ expect(events).toContainEqual(['on_items_complete', ['item1', 'item2', 'item3']]);
+ });
+
+ test('streaming object complete value', () => {
+ const container = StreamingContainerWithAtom.create();
+ const events: any[] = [];
+
+ container.title.onComplete((title: string) => {
+ events.push(['on_title_complete', title]);
+ });
+
+ container.item.onComplete((item: any) => {
+ events.push(['on_item_complete', item]);
+ });
+
+ container.update({ title: 'Title' });
+ expect(events).toEqual([]);
+
+ container.update({ title: 'Title', item: {} });
+ expect(events).toContainEqual(['on_title_complete', 'Title']);
+
+ events.length = 0; // Clear events
+
+ container.update({ title: 'Title', item: { name: 'item1' } });
+ expect(events).toEqual([]);
+
+ container.complete();
+ expect(events).toContainEqual(['on_item_complete', { name: 'item1' }]);
+ });
+
+ test('null streaming list with complete item', () => {
+ const container = StreamingContainer.create();
+ const events: any[] = [];
+
+ container.items.onAppend((item: string, index: number) => {
+ events.push(['on_item_append', item, index]);
+ });
+
+ container.items.onComplete((items: string[]) => {
+ events.push(['on_items_complete', items]);
+ });
+
+ container.update({ items: null as any });
+ container.complete();
+ expect(events).toContainEqual(['on_items_complete', []]);
+ });
+
+ test('null streaming list with streaming item', () => {
+ const container = StreamingContainerWithStringList.create();
+ const events: any[] = [];
+
+ container.items.onAppend((item: StreamingString, index: number) => {
+ events.push(['on_item_append', item, index]);
+ });
+
+ container.items.onComplete((items) => {
+ events.push(['on_items_complete', items]);
+ });
+
+ container.update({ items: null as any });
+ container.complete();
+ expect(events).toContainEqual(['on_items_complete', []]);
+ });
+
+ test('null complete value', () => {
+ const container = StreamingContainerWithNullableAtom.create();
+ const events: any[] = [];
+
+ container.item.onComplete((item: string | null) => {
+ events.push(['on_item_complete', item]);
+ });
+
+ container.update({ item: null });
+ expect(events).toEqual([]);
+
+ container.complete();
+ expect(events).toContainEqual(['on_item_complete', null]);
+ });
+
+ test('atom list complete value', () => {
+ const container = StreamingContainerWithAtomList.create();
+ const events: any[] = [];
+
+ container.items.onComplete((items: any[]) => {
+ events.push(['on_items_complete', items]);
+ });
+
+ container.update({ items: [{ name: 'item1' }, { name: 'item2' }] });
+ expect(events).toEqual([]);
+
+ container.complete();
+ expect(events).toContainEqual([
+ 'on_items_complete',
+ [{ name: 'item1' }, { name: 'item2' }],
+ ]);
+ });
+
+ test('null streaming string', () => {
+ const container = StreamingContainerWithString.create();
+ const events: any[] = [];
+
+ container.item.onAppend((chunk: string) => {
+ events.push(['on_item_append', chunk]);
+ });
+
+ container.item.onComplete((item: string | null) => {
+ events.push(['on_item_complete', item]);
+ });
+
+ container.update({ item: null });
+ expect(events).toEqual([]);
+
+ container.complete();
+ expect(events).toContainEqual(['on_item_complete', null]);
+ });
+
+ describe('fromZod', () => {
+ test('should create StreamingObject from simple Zod schema', () => {
+ const schema = z.object({
+ name: z.string(),
+ age: z.number(),
+ active: z.boolean()
+ });
+
+ const DynamicClass = ld.fromZod(schema);
+ const instance = DynamicClass.create();
+
+ expect(instance).toBeInstanceOf(StreamingObject);
+ expect((instance as any).name).toBeInstanceOf(StreamingString);
+ expect((instance as any).age).toBeInstanceOf(Atom);
+ expect((instance as any).active).toBeInstanceOf(Atom);
+ });
+
+ test('should create StreamingObject with array fields', () => {
+ const schema = z.object({
+ tags: z.array(z.string()),
+ scores: z.array(z.number())
+ });
+
+ const DynamicClass = ld.fromZod(schema);
+ const instance = DynamicClass.create();
+
+ expect((instance as any).tags).toBeInstanceOf(StreamingList);
+ expect((instance as any).scores).toBeInstanceOf(StreamingAtomList);
+ });
+
+ test('should create StreamingObject with nested objects', () => {
+ const addressSchema = z.object({
+ street: z.string(),
+ city: z.string()
+ });
+
+ const userSchema = z.object({
+ name: z.string(),
+ address: addressSchema
+ });
+
+ const DynamicUserClass = ld.fromZod(userSchema);
+ const user = DynamicUserClass.create();
+
+ expect((user as any).name).toBeInstanceOf(StreamingString);
+ expect((user as any).address).toBeInstanceOf(StreamingObject);
+ });
+
+ test('should handle optional fields', () => {
+ const schema = z.object({
+ name: z.string(),
+ nickname: z.string().optional()
+ });
+
+ const DynamicClass = ld.fromZod(schema);
+ const instance = DynamicClass.create();
+
+ expect((instance as any).name).toBeInstanceOf(StreamingString);
+ expect((instance as any).nickname).toBeInstanceOf(StreamingString);
+ });
+
+ test('should handle nullable fields', () => {
+ const schema = z.object({
+ name: z.string(),
+ description: z.string().nullable()
+ });
+
+ const DynamicClass = ld.fromZod(schema);
+ const instance = DynamicClass.create();
+
+ expect((instance as any).name).toBeInstanceOf(StreamingString);
+ expect((instance as any).description).toBeInstanceOf(StreamingString);
+ });
+
+ test('should handle union types', () => {
+ const schema = z.object({
+ data: z.union([z.string(), z.number()])
+ });
+
+ const DynamicClass = ld.fromZod(schema);
+ const instance = DynamicClass.create();
+
+ // unions are currently only supported as atoms
+ expect(instance.data).toBeInstanceOf(Atom);
+ });
+
+ test('should work with Parser for streaming', () => {
+ const schema = z.object({
+ title: z.string(),
+ items: z.array(z.string()),
+ count: z.number()
+ });
+
+ const DynamicClass = ld.fromZod(schema);
+ const instance = DynamicClass.create();
+ const events: any[] = [];
+
+ (instance as any).title.onAppend((chunk: string) => {
+ events.push(['title_append', chunk]);
+ });
+
+ (instance as any).items.onAppend((item: StreamingString, index: number) => {
+ events.push(['item_append', index]);
+ item.onAppend((chunk: string) => {
+ events.push(['item_chunk', index, chunk]);
+ });
+ });
+
+ (instance as any).count.onComplete((value: number) => {
+ events.push(['count_complete', value]);
+ });
+
+ const parser = new Parser(instance);
+ const jsonData = '{"title": "Test Title", "items": ["item1", "item2"], "count": 42}';
+
+ for (let i = 0; i < jsonData.length; i++) {
+ parser.push(jsonData[i]);
+ }
+ parser.complete();
+
+ // Check that title was streamed in chunks
+ const titleChunks = events.filter(e => e[0] === 'title_append').map(e => e[1]).join('');
+ expect(titleChunks).toBe('Test Title');
+ expect(events).toContainEqual(['item_append', 0]);
+ expect(events).toContainEqual(['item_append', 1]);
+ expect(events).toContainEqual(['count_complete', 42]);
+
+ // Check that first item was streamed correctly
+ const item0Chunks = events.filter(e => e[0] === 'item_chunk' && e[1] === 0).map(e => e[2]).join('');
+ expect(item0Chunks).toBe('item1');
+
+ // Check that second item was streamed correctly
+ const item1Chunks = events.filter(e => e[0] === 'item_chunk' && e[1] === 1).map(e => e[2]).join('');
+ expect(item1Chunks).toBe('item2');
+ });
+
+ test('should handle nested object arrays', () => {
+ const itemSchema = z.object({
+ id: z.string(),
+ name: z.string()
+ });
+
+ const containerSchema = z.object({
+ items: z.array(itemSchema)
+ });
+
+ const DynamicClass = ld.fromZod(containerSchema);
+ const instance = DynamicClass.create();
+
+ expect(instance.items).toBeInstanceOf(StreamingList);
+
+ const events: any[] = [];
+ instance.items.onAppend((item, index) => {
+ events.push(['item_added', index]);
+ expect(item).toBeInstanceOf(StreamingObject);
+ });
+
+ const parser = new Parser(instance);
+ const jsonData = '{"items": [{"id": "1", "name": "First"}, {"id": "2", "name": "Second"}]}';
+
+ for (let i = 0; i < jsonData.length; i++) {
+ parser.push(jsonData[i]);
+ }
+ parser.complete();
+
+ expect(events).toContainEqual(['item_added', 0]);
+ expect(events).toContainEqual(['item_added', 1]);
+ });
+ });
+});
diff --git a/ts/tests/zod-integration.test.ts b/ts/tests/zod-integration.test.ts
new file mode 100644
index 0000000..27b35d6
--- /dev/null
+++ b/ts/tests/zod-integration.test.ts
@@ -0,0 +1,415 @@
+import { z } from 'zod';
+import { fromZod, StreamingString, Atom, StreamingList, StreamingObject } from '../src';
+import * as ld from '../src';
+
+describe('fromZod Integration', () => {
+ describe('Basic Types', () => {
+ it('should create StreamingObject from simple Zod schema', () => {
+ const schema = z.object({
+ name: z.string().describe('a name'),
+ age: z.number().describe('an age'),
+ active: z.boolean().describe('an active status')
+ }).describe('A simple user object');
+
+ const StreamingUser = fromZod(schema);
+ const user = StreamingUser.create();
+
+ // Check that fields are properly initialized
+ expect(user.name).toBeInstanceOf(StreamingString);
+ expect(user.age).toBeInstanceOf(Atom);
+ expect(user.active).toBeInstanceOf(Atom);
+ });
+
+ it('should handle string fields with proper streaming', () => {
+ const schema = z.object({
+ message: z.string()
+ });
+
+ const StreamingMessage = fromZod(schema);
+ const message = StreamingMessage.create();
+
+ const chunks: string[] = [];
+ message.message.onAppend((chunk) => {
+ chunks.push(chunk);
+ });
+
+ let completed = false;
+ message.message.onComplete((value) => {
+ completed = true;
+ expect(value).toBe('Hello World');
+ });
+
+ message.update({ message: 'Hello' });
+ message.update({ message: 'Hello World' });
+ message.complete();
+
+ expect(chunks).toEqual(['Hello', ' World']);
+ expect(completed).toBe(true);
+ });
+
+ it('should handle number and boolean atoms', () => {
+ const schema = z.object({
+ count: z.number(),
+ enabled: z.boolean()
+ });
+
+ const StreamingData = fromZod(schema);
+ const data = StreamingData.create();
+
+ let countCompleted = false;
+ let enabledCompleted = false;
+
+ data.count.onComplete((value) => {
+ countCompleted = true;
+ expect(value).toBe(42);
+ });
+
+ data.enabled.onComplete((value) => {
+ enabledCompleted = true;
+ expect(value).toBe(true);
+ });
+
+ data.update({ count: 42, enabled: true });
+ data.complete();
+
+ expect(countCompleted).toBe(true);
+ expect(enabledCompleted).toBe(true);
+ });
+ });
+
+ describe('Array Types', () => {
+ it('should create StreamingList for string arrays', () => {
+ const schema = z.object({
+ tags: z.array(z.string())
+ });
+
+ const StreamingPost = fromZod(schema);
+ const post = StreamingPost.create();
+
+ expect(post.tags).toBeInstanceOf(StreamingList);
+
+ const appendedTags: string[] = [];
+ post.tags.onAppend((tag, index) => {
+ expect(tag).toBeInstanceOf(StreamingString);
+
+ tag.onAppend((chunk) => {
+ if (!appendedTags[index]) appendedTags[index] = '';
+ appendedTags[index] += chunk;
+ });
+ });
+
+ post.update({ tags: ['react'] });
+ post.update({ tags: ['react', 'typescript'] });
+ post.complete();
+
+ expect(appendedTags).toEqual(['react', 'typescript']);
+ });
+
+ it('should handle arrays of objects', () => {
+ const schema = z.object({
+ users: z.array(z.object({
+ name: z.string(),
+ email: z.string()
+ }))
+ });
+
+ const StreamingUserList = fromZod(schema);
+ const userList = StreamingUserList.create();
+
+ const users: Array<{ name: string; email: string }> = [];
+
+ userList.users.onAppend((user, index) => {
+ users[index] = { name: '', email: '' };
+
+ user.name.onAppend((chunk) => {
+ users[index].name += chunk;
+ });
+
+ user.email.onAppend((chunk) => {
+ users[index].email += chunk;
+ });
+ });
+
+ userList.update({
+ users: [
+ { name: 'John', email: 'john@example.com' }
+ ]
+ });
+
+ userList.update({
+ users: [
+ { name: 'John', email: 'john@example.com' },
+ { name: 'Jane', email: 'jane@example.com' }
+ ]
+ });
+
+ userList.complete();
+
+ expect(users).toEqual([
+ { name: 'John', email: 'john@example.com' },
+ { name: 'Jane', email: 'jane@example.com' }
+ ]);
+ });
+ });
+
+ describe('Nested Objects', () => {
+ it('should handle nested object structures', () => {
+ const schema = z.object({
+ user: z.object({
+ profile: z.object({
+ name: z.string(),
+ bio: z.string()
+ }),
+ settings: z.object({
+ theme: z.string(),
+ notifications: z.boolean()
+ })
+ })
+ });
+
+ const StreamingNestedData = fromZod(schema);
+ const data = StreamingNestedData.create();
+
+ expect(data.user).toBeInstanceOf(StreamingObject);
+ expect(data.user.profile).toBeInstanceOf(StreamingObject);
+ expect(data.user.settings).toBeInstanceOf(StreamingObject);
+ expect(data.user.profile.name).toBeInstanceOf(StreamingString);
+ expect(data.user.profile.bio).toBeInstanceOf(StreamingString);
+ expect(data.user.settings.theme).toBeInstanceOf(StreamingString);
+ expect(data.user.settings.notifications).toBeInstanceOf(Atom);
+
+ let nameComplete: string | null = '';
+ let bioComplete: string | null = '';
+ let themeComplete: string | null = '';
+ let notificationsComplete = false;
+
+ data.user.profile.name.onComplete((value) => {
+ nameComplete = value;
+ });
+
+ data.user.profile.bio.onComplete((value) => {
+ bioComplete = value;
+ });
+
+ data.user.settings.theme.onComplete((value) => {
+ themeComplete = value;
+ });
+
+ data.user.settings.notifications.onComplete((value) => {
+ notificationsComplete = value;
+ });
+
+ data.update({
+ user: {
+ profile: {
+ name: 'Alice',
+ bio: 'Software Developer'
+ },
+ settings: {
+ theme: 'dark',
+ notifications: true
+ }
+ }
+ });
+
+ data.complete();
+
+ expect(nameComplete).toBe('Alice');
+ expect(bioComplete).toBe('Software Developer');
+ expect(themeComplete).toBe('dark');
+ expect(notificationsComplete).toBe(true);
+ });
+ });
+
+ describe('Optional and Nullable Types', () => {
+ it('should handle optional fields', () => {
+ const schema = z.object({
+ title: z.string(),
+ subtitle: z.string().optional(),
+ description: z.string().nullable()
+ });
+
+ const StreamingContent = fromZod(schema);
+ const content = StreamingContent.create();
+
+ expect(content.title).toBeInstanceOf(StreamingString);
+ expect(content.subtitle).toBeInstanceOf(StreamingString);
+ expect(content.description).toBeInstanceOf(StreamingString);
+
+ // Test with partial data
+ content.update({
+ title: 'Main Title',
+ description: null
+ });
+
+ content.complete();
+
+ expect(content.title.value).toBe('Main Title');
+ expect(content.description.value).toBe(null);
+ });
+ });
+
+ describe('Complex Real-world Example', () => {
+ it('should handle a blog post with comments', () => {
+ const CommentSchema = z.object({
+ id: z.number(),
+ author: z.string(),
+ content: z.string(),
+ timestamp: z.string()
+ });
+
+ const BlogPostSchema = z.object({
+ title: z.string(),
+ content: z.string(),
+ author: z.object({
+ name: z.string(),
+ email: z.string()
+ }),
+ tags: z.array(z.string()),
+ comments: z.array(CommentSchema),
+ metadata: z.object({
+ createdAt: z.string(),
+ updatedAt: z.string(),
+ published: z.boolean()
+ })
+ });
+
+ const StreamingBlogPost = fromZod(BlogPostSchema);
+ const post = StreamingBlogPost.create();
+
+ // Track streaming events
+ const events: string[] = [];
+
+ post.title.onAppend((chunk) => {
+ events.push(`title: ${chunk}`);
+ });
+
+ post.author.name.onAppend((chunk) => {
+ events.push(`author.name: ${chunk}`);
+ });
+
+ post.tags.onAppend((tag, index) => {
+ tag.onAppend((chunk) => {
+ events.push(`tags[${index}]: ${chunk}`);
+ });
+ });
+
+ post.comments.onAppend((comment, index) => {
+ comment.author.onAppend((chunk) => {
+ events.push(`comments[${index}].author: ${chunk}`);
+ });
+ comment.content.onAppend((chunk) => {
+ events.push(`comments[${index}].content: ${chunk}`);
+ });
+ });
+
+ // Simulate realistic streaming updates
+ post.update({
+ title: 'My Blog',
+ author: { name: 'John', email: 'john@example.com' },
+ content: 'Content here...',
+ tags: ['tech'],
+ comments: [],
+ });
+
+ post.update({
+ title: 'My Blog Post',
+ author: { name: 'John', email: 'john@example.com' },
+ content: 'Content here...',
+ tags: ['tech', 'javascript'],
+ comments: [
+ { id: 1, author: 'Alice', content: 'Great!', timestamp: '2024-01-02' }
+ ],
+ metadata: { createdAt: '2024-01-01', updatedAt: '2024-01-01', published: true }
+ });
+
+ post.complete();
+
+ // Verify that streaming events occurred
+ expect(events.length).toBeGreaterThan(0);
+ expect(events.some(e => e.includes('title:'))).toBe(true);
+ expect(events.some(e => e.includes('author.name:'))).toBe(true);
+
+ // Check tags - at least one tag event should occur
+ const tagEvents = events.filter(e => e.includes('tags['));
+ expect(tagEvents.length).toBeGreaterThanOrEqual(1);
+ expect(events.some(e => e.includes('tags[0]: tech'))).toBe(true);
+
+ // Check comments - if comments were processed
+ expect(events.some(e => e.includes('comments[0].author: Alice'))).toBe(true);
+ expect(events.some(e => e.includes('comments[0].content: Great!'))).toBe(true);
+ });
+ });
+
+ describe('Type Safety and Error Handling', () => {
+ it('should preserve Zod schema for validation', () => {
+ const schema = z.object({
+ email: z.string().email(),
+ age: z.number().min(0).max(120)
+ });
+
+ const StreamingUser = fromZod(schema);
+ const zodSchema = StreamingUser.toZod();
+
+ expect(() => zodSchema.parse({ email: 'invalid-email', age: 25 })).toThrow();
+ expect(() => zodSchema.parse({ email: 'test@example.com', age: -1 })).toThrow();
+ expect(zodSchema.parse({ email: 'test@example.com', age: 25 })).toEqual({
+ email: 'test@example.com',
+ age: 25
+ });
+ });
+
+ it('should handle edge cases gracefully', () => {
+ const schema = z.object({
+ data: z.array(z.string())
+ });
+
+ const StreamingData = fromZod(schema);
+ const data = StreamingData.create();
+
+ // Test empty array
+ data.update({ data: [] });
+ data.complete();
+
+ expect(data.data.value).toEqual([]);
+ });
+ });
+
+ describe('Performance and Memory', () => {
+ it('should handle large nested structures efficiently', () => {
+ const schema = z.object({
+ items: z.array(z.object({
+ id: z.number(),
+ data: z.array(z.string())
+ }))
+ });
+
+ const StreamingLargeData = fromZod(schema);
+ const data = StreamingLargeData.create();
+
+ const itemCount = 100;
+ const items = Array.from({ length: itemCount }, (_, i) => ({
+ id: i,
+ data: [`item-${i}-data-1`, `item-${i}-data-2`]
+ }));
+
+ data.update({ items });
+ data.complete();
+
+ expect(data.items.value).toHaveLength(itemCount);
+ });
+ });
+});
+
+describe('toZod', () => {
+ it('should convert StreamingObject to Zod schema', () => {
+ const schema = ld.object({
+ description: ld.string().describe('field description'),
+ }).describe('object description').toZod();
+ expect(schema).toBeInstanceOf(z.ZodObject);
+ if (!(schema instanceof z.ZodObject)) throw new Error('Not a ZodObject');
+ expect(schema.description).toBe('object description');
+ expect(schema.shape.description).toBeInstanceOf(z.ZodString);
+ expect(schema.shape.description.description).toBe('field description');
+ });
+});
diff --git a/ts/tsconfig.json b/ts/tsconfig.json
new file mode 100644
index 0000000..3c22a92
--- /dev/null
+++ b/ts/tsconfig.json
@@ -0,0 +1,35 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "commonjs",
+ "lib": ["ES2020"],
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "removeComments": false,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "strictFunctionTypes": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "moduleResolution": "node",
+ "allowSyntheticDefaultImports": true,
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true
+ },
+ "include": [
+ "src/**/*"
+ ],
+ "exclude": [
+ "node_modules",
+ "dist",
+ "**/*.test.ts",
+ "**/*.spec.ts"
+ ]
+}