diff --git a/.github/workflows/deploy-chat-production.yml b/.github/workflows/deploy-chat-production.yml
index 74279467845..0b109b9acce 100644
--- a/.github/workflows/deploy-chat-production.yml
+++ b/.github/workflows/deploy-chat-production.yml
@@ -65,11 +65,11 @@ jobs:
needs: [deploy-chat-production, deploy-chat-lambda]
runs-on: ubuntu-latest
steps:
- - run: curl -m 10 --retry 5 ${{ secrets.CHAT_DEPLOY_PRODUCTION_PING_URL }}
+ - run: curl -m 10 --retry 5 "${{ secrets.CHAT_DEPLOY_PRODUCTION_PING_URL }}"
ping-failure:
needs: [deploy-chat-production, deploy-chat-lambda]
if: ${{ failure() }}
runs-on: ubuntu-latest
steps:
- - run: curl -m 10 --retry 5 ${{ secrets.CHAT_DEPLOY_PRODUCTION_PING_URL }}/fail
+ - run: curl -m 10 --retry 5 "${{ secrets.CHAT_DEPLOY_PRODUCTION_PING_URL }}/fail"
diff --git a/.github/workflows/deploy-chat-staging.yml b/.github/workflows/deploy-chat-staging.yml
index 8bbd4889ac4..279c5ccc095 100644
--- a/.github/workflows/deploy-chat-staging.yml
+++ b/.github/workflows/deploy-chat-staging.yml
@@ -69,11 +69,11 @@ jobs:
needs: [deploy-chat-staging, deploy-chat-lambda]
runs-on: ubuntu-latest
steps:
- - run: curl -m 10 --retry 5 ${{ secrets.CHAT_DEPLOY_STAGING_PING_URL }}
+ - run: curl -m 10 --retry 5 "${{ secrets.CHAT_DEPLOY_STAGING_PING_URL }}"
ping-failure:
needs: [deploy-chat-staging, deploy-chat-lambda]
if: ${{ failure() }}
runs-on: ubuntu-latest
steps:
- - run: curl -m 10 --retry 5 ${{ secrets.CHAT_DEPLOY_STAGING_PING_URL }}/fail
+ - run: curl -m 10 --retry 5 "${{ secrets.CHAT_DEPLOY_STAGING_PING_URL }}/fail"
diff --git a/.github/workflows/deploy-integrations-production.yml b/.github/workflows/deploy-integrations-production.yml
index a49a74805c4..71b885a51e0 100644
--- a/.github/workflows/deploy-integrations-production.yml
+++ b/.github/workflows/deploy-integrations-production.yml
@@ -31,7 +31,7 @@ jobs:
uses: ./.github/actions/deploy-integrations
with:
environment: 'production'
- extra_filter: "-F '!docusign' -F '!zendesk' -F '!chat' -F '!grafana'"
+ extra_filter: "-F '!docusign' -F '!chat' -F '!grafana'"
force: ${{ github.event.inputs.force == 'true' }}
sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }}
token_cloud_ops_account: ${{ secrets.PRODUCTION_TOKEN_CLOUD_OPS_ACCOUNT }}
diff --git a/.github/workflows/prod-master-version-verification.yml b/.github/workflows/prod-master-version-verification.yml
index 2d9c29a04ee..f789d77b344 100644
--- a/.github/workflows/prod-master-version-verification.yml
+++ b/.github/workflows/prod-master-version-verification.yml
@@ -24,7 +24,7 @@ jobs:
REPO: ${{ github.repository }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WORKFLOW_SEAGULL_WEBHOOK_URL }}
run: |
- SKIP_INTEGRATIONS=("chat" "docusign" "zendesk" "zendesk-messaging-hitl")
+ SKIP_INTEGRATIONS=("chat" "docusign" "zendesk-messaging-hitl")
integrations=$(find integrations -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -n1 basename | sort -u)
should_fail=0
diff --git a/CODEOWNERS b/CODEOWNERS
index ccd516325f2..c8df29deb03 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -17,9 +17,9 @@
## AI
/packages/cognitive @botpress/dragon
-/packages/llmz @slvnperron @botpress/swordfish
-/packages/vai @slvnperron @botpress/swordfish
-/packages/zai @slvnperron @botpress/swordfish
+/packages/llmz @slvnperron @botpress/orca @botpress/swordfish
+/packages/vai @slvnperron @botpress/orca @botpress/swordfish
+/packages/zai @slvnperron @botpress/orca @botpress/swordfish
# Bots
/bots @botpress/shell
diff --git a/integrations/monday/integration.definition.ts b/integrations/monday/integration.definition.ts
index d147f0ad981..743555652f9 100644
--- a/integrations/monday/integration.definition.ts
+++ b/integrations/monday/integration.definition.ts
@@ -5,7 +5,7 @@ export default new IntegrationDefinition({
name: 'monday',
title: 'Monday',
description: 'Manage items in Monday boards.',
- version: '1.1.3',
+ version: '1.1.4',
readme: 'hub.md',
icon: 'icon.svg',
states: {
diff --git a/integrations/monday/linkTemplate.vrl b/integrations/monday/linkTemplate.vrl
index 06be15fb414..23372049f7a 100644
--- a/integrations/monday/linkTemplate.vrl
+++ b/integrations/monday/linkTemplate.vrl
@@ -1,4 +1,4 @@
webhookId = to_string!(.webhookId)
webhookUrl = to_string!(.webhookUrl)
-"{{ webhookUrl }}/oauth/wizard/oauth-redirect?state={{ webhookId }}"
+"{{ webhookUrl }}/oauth/wizard/start?state={{ webhookId }}"
diff --git a/packages/client/package.json b/packages/client/package.json
index 0bf140dfdd8..89e98f8401a 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -16,7 +16,7 @@
},
"scripts": {
"check:type": "tsc --noEmit",
- "build:type": "tsup --tsconfig tsconfig.build.json ./src/index.ts --dts-resolve --dts-only",
+ "build:type": "rollup -c rollup.dts.config.mjs",
"build:browser": "ts-node -T ./build.ts --browser",
"build:node": "ts-node -T ./build.ts --node",
"build:bundle": "ts-node -T ./build.ts --bundle",
@@ -34,7 +34,8 @@
"@types/qs": "^6.9.7",
"esbuild": "^0.25.10",
"lodash": "^4.17.21",
- "tsup": "^8.0.2"
+ "rollup": "^4.60.4",
+ "rollup-plugin-dts": "^6.4.1"
},
"engines": {
"node": ">=18.0.0"
diff --git a/packages/client/rollup.dts.config.mjs b/packages/client/rollup.dts.config.mjs
new file mode 100644
index 00000000000..13e8fc6fbab
--- /dev/null
+++ b/packages/client/rollup.dts.config.mjs
@@ -0,0 +1,15 @@
+import dts from 'rollup-plugin-dts'
+
+export default {
+ input: './src/index.ts',
+ external: [/node_modules/],
+ output: {
+ file: './dist/index.d.ts',
+ },
+ plugins: [
+ dts({
+ tsconfig: './tsconfig.build.json',
+ respectExternal: true,
+ }),
+ ],
+}
diff --git a/packages/llmz/examples/21_chat_tool_components/README.md b/packages/llmz/examples/21_chat_tool_components/README.md
new file mode 100644
index 00000000000..113c9c99cd2
--- /dev/null
+++ b/packages/llmz/examples/21_chat_tool_components/README.md
@@ -0,0 +1,38 @@
+## Yielding Components from Tool Handlers
+
+This demo shows how tool handlers can directly yield UI components back to the chat using **async generator functions** (`async function*`). Unlike example 10 where the LLM yields components after calling a tool, here the tool handler itself controls what to display and when.
+
+The `purchase_ticket` tool handler is an async generator that yields progress updates and the final ticket ā the LLM doesn't need to know about components at all.
+
+### Handler pattern
+
+```tsx
+async *handler({ from, date, to }) {
+ // Yield progress components step by step
+ yield ProgressComponent.render({ message: 'Checking flight availability...', step: 1, total: 3 })
+ yield ProgressComponent.render({ message: 'Calculating best price...', step: 2, total: 3 })
+
+ // Yield the final ticket component
+ yield PlaneTicketComponent.render({
+ from, to, date,
+ price: 299.99,
+ ticketNumber: 'TICKET-345633',
+ })
+
+ // Return the tool result for the LLM
+ return { price: 299.99, ticketNumber: 'TICKET-345633', confirmation: '...' }
+}
+```
+
+### Contrast with example 10
+
+| | Example 10 | Example 21 |
+| ------------------------------ | ----------------------------------- | ------------------------------- |
+| Who yields components | LLM-generated JSX code | Tool handler itself |
+| Tool handler type | `async (input) => output` | `async function* (input)` |
+| LLM needs to know components | Yes ā included as component aliases | No ā tool handles it internally |
+| Progress updates mid-execution | Not possible | `yield` between steps |
+
+## š„ Demo
+
+
diff --git a/packages/llmz/examples/21_chat_tool_components/demo.svg b/packages/llmz/examples/21_chat_tool_components/demo.svg
new file mode 100644
index 00000000000..c23e34aefdf
--- /dev/null
+++ b/packages/llmz/examples/21_chat_tool_components/demo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/llmz/examples/21_chat_tool_components/index.ts b/packages/llmz/examples/21_chat_tool_components/index.ts
new file mode 100644
index 00000000000..e0031d3122c
--- /dev/null
+++ b/packages/llmz/examples/21_chat_tool_components/index.ts
@@ -0,0 +1,169 @@
+/**
+ * Example 21: Yielding Components from Tool Handlers
+ *
+ * This example demonstrates how tool handlers can directly yield UI components
+ * back to the chat interface using async generator functions. Unlike example 10
+ * where the LLM yielids components, here the tool handler itself controls what
+ * to display and when.
+ *
+ * Key concepts:
+ * - Tool handlers as async generators (async function*)
+ * - Yielding components mid-execution for progress updates
+ * - Component.render() for constructing RenderedComponent objects
+ * - Tool-side component rendering vs LLM-side component yielding
+ *
+ * Contrast with example 10: the LLM no longer needs to know about components ā
+ * the tool handler takes care of rendering directly.
+ */
+
+import { Client } from '@botpress/client'
+import { z } from '@bpinternal/zui'
+
+import chalk from 'chalk'
+import { Component, execute, Tool } from 'llmz'
+import { box } from '../utils/box'
+import { CLIChat } from '../utils/cli-chat'
+
+// Initialize Botpress client
+const client = new Client({
+ botId: process.env.BOTPRESS_BOT_ID!,
+ token: process.env.BOTPRESS_TOKEN!,
+})
+
+// Define a custom UI component for displaying plane tickets
+const PlaneTicketComponent = new Component({
+ name: 'PlaneTicket',
+ description: 'A component to display a plane ticket',
+ type: 'leaf',
+ leaf: {
+ props: z.object({
+ ticketNumber: z.string().describe('The unique ticket number for the plane ticket'),
+ from: z.string().describe('The departure city'),
+ to: z.string().describe('The destination city'),
+ date: z.string().describe('The date of the flight (in YYYY-MM-DD format)'),
+ price: z.number().optional().describe('The price of the ticket'),
+ }),
+ },
+ examples: [
+ {
+ name: 'PlaneTicket',
+ description: 'A simple plane ticket example',
+ code: '',
+ },
+ ],
+})
+
+// Define a progress component for status updates
+const ProgressComponent = new Component({
+ name: 'Progress',
+ description: 'Displays a progress update message',
+ type: 'leaf',
+ leaf: {
+ props: z.object({
+ message: z.string().describe('The progress message'),
+ step: z.number().describe('Current step number'),
+ total: z.number().describe('Total number of steps'),
+ }),
+ },
+ examples: [
+ {
+ name: 'Basic Progress',
+ description: 'Show a progress bar with step and total',
+ code: '',
+ },
+ {
+ name: 'Mid-progress',
+ description: 'Midway progress update',
+ code: '',
+ },
+ ],
+})
+
+// Tool for purchasing tickets ā uses async generator to yield components
+const purchaseTicket = new Tool({
+ name: 'purchase_ticket',
+ description: 'Purchase a plane ticket, showing progress updates along the way',
+ input: z.object({
+ from: z.string().describe('The departure city'),
+ to: z.string().describe('The destination city'),
+ date: z.string().describe('The date of the flight (in YYYY-MM-DD format)'),
+ }),
+ output: z.object({
+ price: z.number().describe('The price of the purchased ticket in USD'),
+ ticketNumber: z.string().describe('The unique ticket number for the purchased ticket'),
+ confirmation: z.string().describe('Confirmation message for the ticket purchase'),
+ }),
+ // Async generator handler: yields components progress, returns final result
+ async *handler({ from, date, to }) {
+ // Step 1: Show progress
+ yield ProgressComponent.render({ message: 'Checking flight availability...', step: 1, total: 3 })
+ await new Promise((resolve) => setTimeout(resolve, 500))
+
+ // Step 2: Show more progress
+ yield ProgressComponent.render({ message: 'Calculating best price...', step: 2, total: 3 })
+ await new Promise((resolve) => setTimeout(resolve, 500))
+
+ // Step 3: Yiels the ticket component itself
+ yield PlaneTicketComponent.render({
+ from,
+ to,
+ date,
+ price: 299.99,
+ ticketNumber: 'TICKET-345633',
+ })
+
+ // Final return ā the tool result for the LLM
+ return {
+ price: 299.99,
+ ticketNumber: 'TICKET-345633',
+ confirmation: `Ticket from ${from} to ${to} on ${date} purchased successfully!`,
+ }
+ },
+})
+
+const chat = new CLIChat()
+
+chat.transcript.push({
+ role: 'user',
+ content: 'I want to purchase a plane ticket from New York to Los Angeles on 2025-10-01.',
+})
+
+// Register component renderers ā same as example 10
+chat.registerComponent(PlaneTicketComponent, async (message) => {
+ const { ticketNumber, from, to, date, price } = message.props
+
+ const ticket = box([
+ chalk.white.bold(' āļø FLIGHT TICKET'),
+ `${chalk.yellow.bold('Ticket Number:')} ${chalk.white(ticketNumber)}`,
+ '',
+ `${chalk.green.bold('From:')} ${chalk.white(from)}`,
+ `${chalk.red.bold('To:')} ${chalk.white(to)}`,
+ '',
+ `${chalk.magenta.bold('Date:')} ${chalk.white(date)}`,
+ `${chalk.cyan.bold('Price:')} ${chalk.white(`$${price?.toFixed(2) || 'N/A'}`)}`,
+ '',
+ chalk.gray(' Have a safe flight! š«'),
+ ])
+
+ console.log(ticket)
+})
+
+chat.registerComponent(ProgressComponent, async (message) => {
+ const { step, total, message: msg } = message.props
+ const bar = 'ā'.repeat(step) + 'ā'.repeat(Math.max(0, total - step))
+ console.log(chalk.blue(`[${bar}]`) + ' ' + chalk.white(msg))
+})
+
+// Execute the travel agent workflow
+// Note: the LLM no longer needs to know about PlaneTicketComponent ā
+// the tool handler handles rendering directly
+const result = await execute({
+ instructions:
+ 'You are a travel agent. Help the user purchase a plane ticket. The ticket will be displayed automatically when the purchase completes.',
+ tools: [purchaseTicket],
+ chat,
+ client,
+})
+
+console.log("Here's the code generated by the LLMz:")
+console.log(result.iteration?.code)
diff --git a/packages/llmz/examples/record-demo.sh b/packages/llmz/examples/record-demo.sh
index 540eedb17ed..2963e5292dc 100755
--- a/packages/llmz/examples/record-demo.sh
+++ b/packages/llmz/examples/record-demo.sh
@@ -12,7 +12,7 @@ CAST_PATH="${FOLDER}/demo.cast"
SVG_PATH="${FOLDER}/demo.svg"
echo "š¬ Recording demo for ${FOLDER}..."
-asciinema rec "$CAST_PATH" --command "pnpm start ${FOLDER}" --overwrite
+asciinema rec "$CAST_PATH" --command "pnpm start ${FOLDER}" --overwrite -f asciicast-v2
echo "š Appending trailing empty frame..."
node -e "
diff --git a/packages/llmz/package.json b/packages/llmz/package.json
index 20984102072..c4fc32ef104 100644
--- a/packages/llmz/package.json
+++ b/packages/llmz/package.json
@@ -2,7 +2,7 @@
"name": "llmz",
"type": "module",
"description": "LLMz - An LLM-native Typescript VM built on top of Zui",
- "version": "0.0.78",
+ "version": "0.0.79",
"types": "./dist/index.d.ts",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
@@ -74,7 +74,7 @@
"@botpress/client": "1.46.0",
"@botpress/cognitive": "0.5.5",
"@bpinternal/thicktoken": "^2.0.0",
- "@bpinternal/zui": "^2.1.1"
+ "@bpinternal/zui": "^2.2.1"
},
"dependenciesMeta": {
"@bpinternal/zui": {
diff --git a/packages/llmz/src/component.ts b/packages/llmz/src/component.ts
index d2bd2e04ea7..732d38b7bae 100644
--- a/packages/llmz/src/component.ts
+++ b/packages/llmz/src/component.ts
@@ -1,5 +1,5 @@
import { z } from '@bpinternal/zui'
-import { isAnyJsxComponent, isJsxComponent } from './jsx.js'
+import { createJsxComponent, isAnyJsxComponent, isJsxComponent, JsxComponent } from './jsx.js'
export type ExampleUsage = {
name: string
@@ -206,12 +206,7 @@ type ExtractComponentProps =
: never
// Rendered component type
-export type RenderedComponent> = {
- __jsx: true
- type: string
- children: any[]
- props: TProps
-}
+export type RenderedComponent = JsxComponent
// Component Class that infers props from component definition
export class Component {
@@ -222,6 +217,13 @@ export class Component {
assertValidComponent(definition)
this.definition = definition
}
+
+ public render(
+ props: Component['propsType'],
+ children: Array = []
+ ): RenderedComponent {
+ return createJsxComponent({ type: this.definition.name, props, children })
+ }
}
// Type guard function that infers props from component
diff --git a/packages/llmz/src/llmz.ts b/packages/llmz/src/llmz.ts
index 032fd2b7e92..bf40c91d78c 100644
--- a/packages/llmz/src/llmz.ts
+++ b/packages/llmz/src/llmz.ts
@@ -590,6 +590,7 @@ const executeIteration = async ({
for (const tool of obj.tools ?? []) {
instance[tool.name] = wrapTool({
+ chat: ctx.chat,
tool,
traces,
object: obj.name,
@@ -607,7 +608,15 @@ const executeIteration = async ({
}
for (const tool of iteration.tools) {
- const wrapped = wrapTool({ tool, traces, iteration, beforeHook: onBeforeTool, afterHook: onAfterTool, controller })
+ const wrapped = wrapTool({
+ chat: ctx.chat,
+ tool,
+ traces,
+ iteration,
+ beforeHook: onBeforeTool,
+ afterHook: onAfterTool,
+ controller,
+ })
for (const key of [tool.name, ...(tool.aliases ?? [])]) {
vmContext[key] = wrapped
}
@@ -784,6 +793,7 @@ const executeIteration = async ({
}
type Props = {
+ chat?: Chat
tool: Tool
object?: string
traces: Trace[]
@@ -793,7 +803,7 @@ type Props = {
controller: AbortController
}
-function wrapTool({ tool, traces, object, iteration, beforeHook, afterHook, controller }: Props) {
+function wrapTool({ chat, tool, traces, object, iteration, beforeHook, afterHook, controller }: Props) {
const getToolInput = (input: any) => (tool.zInput as any).safeParse(input).data ?? input
return function (input: any) {
@@ -865,9 +875,13 @@ function wrapTool({ tool, traces, object, iteration, beforeHook, afterHook, cont
input = beforeRes.input
}
- let output = await tool.execute(input, {
- callId: toolCallId,
- })
+ let output = await tool.execute(
+ input,
+ {
+ callId: toolCallId,
+ },
+ chat
+ )
const afterRes = await afterHook?.({
iteration,
diff --git a/packages/llmz/src/tool.ts b/packages/llmz/src/tool.ts
index ee100d0b0a8..2057e423fd2 100644
--- a/packages/llmz/src/tool.ts
+++ b/packages/llmz/src/tool.ts
@@ -1,6 +1,8 @@
import { TypeOf, z, transforms, ZodObject, ZodType } from '@bpinternal/zui'
import { JSONSchema7 } from 'json-schema'
import { isEmpty, uniq } from 'lodash-es'
+import { Chat } from './chat.js'
+import { RenderedComponent } from './component.js'
import { Serializable } from './types.js'
import { getTypings as generateTypings } from './typings.js'
import { convertObjectToZuiLiterals, isJsonSchema, isValidIdentifier, isZuiSchema } from './utils.js'
@@ -438,7 +440,10 @@ export class Tool IX)
output: OX | ((original: O | undefined) => OX)
staticInputValues?: SmartPartial>
- handler: (args: TypeOf, ctx: ToolCallContext) => Promise>
+ handler: (
+ args: TypeOf,
+ ctx: ToolCallContext
+ ) => AsyncGenerator> | Promise>
retry: ToolRetryFn>
}> = {}
): Tool {
@@ -478,7 +483,10 @@ export class Tool, ctx: ToolCallContext) => Promise>,
+ handler: (props.handler ?? this._handler) as (
+ args: TypeOf,
+ ctx: ToolCallContext
+ ) => AsyncGenerator, void> | Promise>,
retry: props.retry ?? this.retry,
}).setStaticInputValues((props.staticInputValues as any) ?? (this._staticInputValues as any))
} catch (e) {
@@ -486,7 +494,10 @@ export class Tool Promise
+ private _handler: (
+ args: unknown,
+ ctx: ToolCallContext
+ ) => AsyncGenerator | Promise
/**
* Creates a new Tool instance.
@@ -572,7 +583,10 @@ export class Tool>
- handler: (args: TypeOf, ctx: ToolCallContext) => Promise>
+ handler: (
+ args: TypeOf,
+ ctx: ToolCallContext
+ ) => AsyncGenerator> | Promise>
retry?: ToolRetryFn>
}) {
if (!isValidIdentifier(props.name)) {
@@ -650,6 +664,34 @@ export class Tool void
+ ): Promise {
+ const handler = this._handler(input, ctx)
+ const isGen = typeof handler === 'object' && handler !== null && 'next' in handler
+
+ if (!isGen) {
+ return handler
+ }
+
+ let yieldIndex = 0
+ while (true) {
+ const { value, done } = await handler.next()
+ if (done) {
+ return value
+ }
+ if (yieldIndex >= yieldedCount) {
+ setYieldedCount(yieldIndex + 1)
+ await chat?.handler?.(value)
+ }
+ yieldIndex++
+ }
+ }
+
/**
* Executes the tool with the given input and context.
*
@@ -674,7 +716,7 @@ export class Tool, ctx: ToolCallContext): Promise> {
+ public async execute(rawInput: TypeOf, ctx: ToolCallContext, chat?: Chat): Promise> {
const isZodObject = (this.zInput as any)._def.typeName === 'ZodObject'
const input = isZodObject ? (rawInput ?? {}) : rawInput
@@ -685,10 +727,13 @@ export class Tool {
+ yieldedCount = n
+ })
const pOutput = (this.zOutput as any).safeParse(result)
return pOutput.success ? pOutput.data : result
} catch (err) {
diff --git a/packages/sdk/package.json b/packages/sdk/package.json
index 7e3221c4500..f937b32c8cd 100644
--- a/packages/sdk/package.json
+++ b/packages/sdk/package.json
@@ -31,7 +31,7 @@
"tsup": "^8.0.2"
},
"peerDependencies": {
- "@bpinternal/zui": "^2.1.1",
+ "@bpinternal/zui": "^2.2.1",
"esbuild": "^0.16.12"
},
"peerDependenciesMeta": {
diff --git a/packages/vai/package.json b/packages/vai/package.json
index 27f538081ce..c4166d0829f 100644
--- a/packages/vai/package.json
+++ b/packages/vai/package.json
@@ -42,7 +42,7 @@
"peerDependencies": {
"@botpress/client": "1.46.0",
"@bpinternal/thicktoken": "^1.0.1",
- "@bpinternal/zui": "^2.1.1",
+ "@bpinternal/zui": "^2.2.1",
"lodash": "^4.17.21",
"vitest": "^2 || ^3 || ^4 || ^5"
},
diff --git a/packages/zai/e2e/data/cache.jsonl b/packages/zai/e2e/data/cache.jsonl
index 7cab2bc9e33..78c7514dded 100644
--- a/packages/zai/e2e/data/cache.jsonl
+++ b/packages/zai/e2e/data/cache.jsonl
@@ -2020,3 +2020,5 @@
{"key":"f4dd48a5","input":"{\"body\":{\"messages\":[{\"content\":\"You are a code patching assistant. Your task is to generate precise line-based patches using the Micropatch protocol.\\n\\n## Input Format\\n\\nYou will receive files in this XML format:\\n\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n\\n\\n\\n001|export function add(a, b) {\\n002| return a + b\\n003|}\\n\\n```\\n\\nEach file has:\\n- **path**: Full file path\\n- **name**: File name\\n- **Numbered lines**: Format is `NNN|content` where NNN is the ORIGINAL line number (1-based)\\n\\n## Output Format\\n\\nGenerate patches for EACH file that needs modification using this EXACT XML format:\\n\\n```\\n\\nā¼ļø=1|const a = 1\\nā¼ļø=2|const b = 2\\nā¼ļø=3|console.log(a + b)\\n\\n\\n\\nā¼ļø<1|/**\\n * Adds two numbers\\n */\\n\\n```\\n\\n**CRITICAL RULES**:\\n1. Each `` tag MUST include the exact `path` attribute from the input\\n2. Put patch operations for EACH file inside its own `...` block\\n3. If a file doesn't need changes, omit its `` block entirely\\n4. DO NOT mix patches from different files\\n5. DO NOT include line numbers or any text outside the patch operations\\n\\n## Micropatch Protocol\\n\\nThe Micropatch protocol uses line numbers to reference ORIGINAL lines (before any edits).\\n\\n### Operations\\n\\nEach operation starts with the marker `ā¼ļø` at the beginning of a line:\\n\\n1. **Insert BEFORE line**: `ā¼ļøNNN|text`\\n - Inserts `text` as a new line AFTER original line NNN\\n - Example: `ā¼ļø>10|}`\\n\\n3. **Replace single line**: `ā¼ļø=NNN|new text`\\n - Replaces original line NNN with `new text`\\n - Can span multiple lines (continue until next ā¼ļø or end)\\n - Example:\\n ```\\n ā¼ļø=7|function newName() {\\n return 42\\n }\\n ```\\n\\n4. **Replace range**: `ā¼ļø=NNN-MMM|replacement`\\n - Replaces lines NNN through MMM with replacement text\\n - Example: `ā¼ļø=5-8|const combined = a + b + c + d`\\n\\n5. **Delete single line**: `ā¼ļø-NNN`\\n - Deletes original line NNN\\n - Example: `ā¼ļø-12`\\n\\n6. **Delete range**: `ā¼ļø-NNN-MMM`\\n - Deletes lines NNN through MMM inclusive\\n - Example: `ā¼ļø-5-10`\\n\\n### Escaping\\n\\n- To include a literal `ā¼ļø` in your text, use `\\\\ā¼ļø`\\n- No other escape sequences are recognized\\n\\n### Important Rules\\n\\n1. **Use ORIGINAL line numbers**: Always reference the line numbers shown in the input (001, 002, etc.)\\n2. **One operation per line**: Each operation must start on a new line with `ā¼ļø`\\n3. **No explanations**: Output ONLY patch operations inside `` tags\\n4. **Precise operations**: Use the minimal set of operations to achieve the goal\\n5. **Verify line numbers**: Double-check that line numbers match the input\\n\\n## Example\\n\\n**Input:**\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n004|\\n005|export { x, y }\\n\\n```\\n\\n**Task:** Change variable names from x,y to a,b\\n\\n**Output:**\\n```\\n\\nā¼ļø=1|const a = 1\\nā¼ļø=2|const b = 2\\nā¼ļø=3|console.log(a + b)\\nā¼ļø=5|export { a, b }\\n\\n```\\n\\n## Your Task\\n\\nGenerate ONLY the `` blocks with patch operations. Do not include explanations, comments, or any other text.\",\"role\":\"system\"},{\"content\":\"Instructions: change database host to \\\"db.prod.internal\\\", port to 5433, and add \\\"caching\\\" to features\\n\\n\\n001|{\\n002| \\\"database\\\": {\\n003| \\\"host\\\": \\\"localhost\\\",\\n004| \\\"port\\\": 5432,\\n005| \\\"credentials\\\": {\\n006| \\\"username\\\": \\\"admin\\\",\\n007| \\\"password\\\": \\\"secret\\\"\\n008| }\\n009| },\\n010| \\\"features\\\": [\\n011| \\\"auth\\\",\\n012| \\\"logging\\\"\\n013| ]\\n014|}\\n\\n\\nGenerate patches for each file that needs modification:\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.patch\",\"promptSource\":\"zai:zai.patch:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"\nā¼ļø=3| \"host\": \"db.prod.internal\",\nā¼ļø=4| \"port\": 5433,\nā¼ļø=12| \"logging\",\nā¼ļø<13| \"caching\"\n","metadata":{"provider":"cerebras","usage":{"inputTokens":1213,"outputTokens":959,"inputCost":0.00042455,"outputCost":0.00071925},"model":"cerebras:gpt-oss-120b","ttft":166,"latency":707,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.0011438,"requestId":"req-a782cd29-593b-4e8c-98df-58f2adf7da86"}}}
{"key":"7ad8ac31","input":"{\"body\":{\"messages\":[{\"content\":\"You are a code patching assistant. Your task is to generate precise line-based patches using the Micropatch protocol.\\n\\n## Input Format\\n\\nYou will receive files in this XML format:\\n\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n\\n\\n\\n001|export function add(a, b) {\\n002| return a + b\\n003|}\\n\\n```\\n\\nEach file has:\\n- **path**: Full file path\\n- **name**: File name\\n- **Numbered lines**: Format is `NNN|content` where NNN is the ORIGINAL line number (1-based)\\n\\n## Output Format\\n\\nGenerate patches for EACH file that needs modification using this EXACT XML format:\\n\\n```\\n\\nā¼ļø=1|const a = 1\\nā¼ļø=2|const b = 2\\nā¼ļø=3|console.log(a + b)\\n\\n\\n\\nā¼ļø<1|/**\\n * Adds two numbers\\n */\\n\\n```\\n\\n**CRITICAL RULES**:\\n1. Each `` tag MUST include the exact `path` attribute from the input\\n2. Put patch operations for EACH file inside its own `...` block\\n3. If a file doesn't need changes, omit its `` block entirely\\n4. DO NOT mix patches from different files\\n5. DO NOT include line numbers or any text outside the patch operations\\n\\n## Micropatch Protocol\\n\\nThe Micropatch protocol uses line numbers to reference ORIGINAL lines (before any edits).\\n\\n### Operations\\n\\nEach operation starts with the marker `ā¼ļø` at the beginning of a line:\\n\\n1. **Insert BEFORE line**: `ā¼ļøNNN|text`\\n - Inserts `text` as a new line AFTER original line NNN\\n - Example: `ā¼ļø>10|}`\\n\\n3. **Replace single line**: `ā¼ļø=NNN|new text`\\n - Replaces original line NNN with `new text`\\n - Can span multiple lines (continue until next ā¼ļø or end)\\n - Example:\\n ```\\n ā¼ļø=7|function newName() {\\n return 42\\n }\\n ```\\n\\n4. **Replace range**: `ā¼ļø=NNN-MMM|replacement`\\n - Replaces lines NNN through MMM with replacement text\\n - Example: `ā¼ļø=5-8|const combined = a + b + c + d`\\n\\n5. **Delete single line**: `ā¼ļø-NNN`\\n - Deletes original line NNN\\n - Example: `ā¼ļø-12`\\n\\n6. **Delete range**: `ā¼ļø-NNN-MMM`\\n - Deletes lines NNN through MMM inclusive\\n - Example: `ā¼ļø-5-10`\\n\\n### Escaping\\n\\n- To include a literal `ā¼ļø` in your text, use `\\\\ā¼ļø`\\n- No other escape sequences are recognized\\n\\n### Important Rules\\n\\n1. **Use ORIGINAL line numbers**: Always reference the line numbers shown in the input (001, 002, etc.)\\n2. **One operation per line**: Each operation must start on a new line with `ā¼ļø`\\n3. **No explanations**: Output ONLY patch operations inside `` tags\\n4. **Precise operations**: Use the minimal set of operations to achieve the goal\\n5. **Verify line numbers**: Double-check that line numbers match the input\\n\\n## Example\\n\\n**Input:**\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n004|\\n005|export { x, y }\\n\\n```\\n\\n**Task:** Change variable names from x,y to a,b\\n\\n**Output:**\\n```\\n\\nā¼ļø=1|const a = 1\\nā¼ļø=2|const b = 2\\nā¼ļø=3|console.log(a + b)\\nā¼ļø=5|export { a, b }\\n\\n```\\n\\n## Your Task\\n\\nGenerate ONLY the `` blocks with patch operations. Do not include explanations, comments, or any other text.\",\"role\":\"system\"},{\"content\":\"Instructions: change database host to \\\"db.prod.internal\\\", port to 5433 or higher, and add \\\"caching\\\" to features\\n\\n\\n001|{\\n002| \\\"database\\\": {\\n003| \\\"host\\\": \\\"localhost\\\",\\n004| \\\"port\\\": 5432,\\n005| \\\"credentials\\\": {\\n006| \\\"username\\\": \\\"admin\\\",\\n007| \\\"password\\\": \\\"secret\\\"\\n008| }\\n009| },\\n010| \\\"features\\\": [\\n011| \\\"auth\\\",\\n012| \\\"logging\\\"\\n013| ]\\n014|}\\n\\n\\nGenerate patches for each file that needs modification:\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.patch\",\"promptSource\":\"zai:zai.patch:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"\nā¼ļø=003| \"host\": \"db.prod.internal\",\nā¼ļø=004| \"port\": 5433,\nā¼ļø=012| \"logging\",\nā¼ļø>012| \"caching\"\n","metadata":{"provider":"cerebras","usage":{"inputTokens":1215,"outputTokens":933,"inputCost":0.00042525,"outputCost":0.00069975},"model":"cerebras:gpt-oss-120b","ttft":259,"latency":742,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.001125,"requestId":"req-8d671d94-4fc3-47af-b66e-f914fdf010a5"}}}
{"key":"3b042987","input":"{\"body\":{\"messages\":[{\"content\":\"You are a code patching assistant. Your task is to generate precise line-based patches using the Micropatch protocol.\\n\\n## Input Format\\n\\nYou will receive files in this XML format:\\n\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n\\n\\n\\n001|export function add(a, b) {\\n002| return a + b\\n003|}\\n\\n```\\n\\nEach file has:\\n- **path**: Full file path\\n- **name**: File name\\n- **Numbered lines**: Format is `NNN|content` where NNN is the ORIGINAL line number (1-based)\\n\\n## Output Format\\n\\nGenerate patches for EACH file that needs modification using this EXACT XML format:\\n\\n```\\n\\nā¼ļø=1|const a = 1\\nā¼ļø=2|const b = 2\\nā¼ļø=3|console.log(a + b)\\n\\n\\n\\nā¼ļø<1|/**\\n * Adds two numbers\\n */\\n\\n```\\n\\n**CRITICAL RULES**:\\n1. Each `` tag MUST include the exact `path` attribute from the input\\n2. Put patch operations for EACH file inside its own `...` block\\n3. If a file doesn't need changes, omit its `` block entirely\\n4. DO NOT mix patches from different files\\n5. DO NOT include line numbers or any text outside the patch operations\\n\\n## Micropatch Protocol\\n\\nThe Micropatch protocol uses line numbers to reference ORIGINAL lines (before any edits).\\n\\n### Operations\\n\\nEach operation starts with the marker `ā¼ļø` at the beginning of a line:\\n\\n1. **Insert BEFORE line**: `ā¼ļøNNN|text`\\n - Inserts `text` as a new line AFTER original line NNN\\n - Example: `ā¼ļø>10|}`\\n\\n3. **Replace single line**: `ā¼ļø=NNN|new text`\\n - Replaces original line NNN with `new text`\\n - Can span multiple lines (continue until next ā¼ļø or end)\\n - Example:\\n ```\\n ā¼ļø=7|function newName() {\\n return 42\\n }\\n ```\\n\\n4. **Replace range**: `ā¼ļø=NNN-MMM|replacement`\\n - Replaces lines NNN through MMM with replacement text\\n - Example: `ā¼ļø=5-8|const combined = a + b + c + d`\\n\\n5. **Delete single line**: `ā¼ļø-NNN`\\n - Deletes original line NNN\\n - Example: `ā¼ļø-12`\\n\\n6. **Delete range**: `ā¼ļø-NNN-MMM`\\n - Deletes lines NNN through MMM inclusive\\n - Example: `ā¼ļø-5-10`\\n\\n### Escaping\\n\\n- To include a literal `ā¼ļø` in your text, use `\\\\ā¼ļø`\\n- No other escape sequences are recognized\\n\\n### Important Rules\\n\\n1. **Use ORIGINAL line numbers**: Always reference the line numbers shown in the input (001, 002, etc.)\\n2. **One operation per line**: Each operation must start on a new line with `ā¼ļø`\\n3. **No explanations**: Output ONLY patch operations inside `` tags\\n4. **Precise operations**: Use the minimal set of operations to achieve the goal\\n5. **Verify line numbers**: Double-check that line numbers match the input\\n\\n## Example\\n\\n**Input:**\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n004|\\n005|export { x, y }\\n\\n```\\n\\n**Task:** Change variable names from x,y to a,b\\n\\n**Output:**\\n```\\n\\nā¼ļø=1|const a = 1\\nā¼ļø=2|const b = 2\\nā¼ļø=3|console.log(a + b)\\nā¼ļø=5|export { a, b }\\n\\n```\\n\\n## Your Task\\n\\nGenerate ONLY the `` blocks with patch operations. Do not include explanations, comments, or any other text.\",\"role\":\"system\"},{\"content\":\"The following JSON file is valid JSON but does not match the expected schema.\\n\\n\\n001|{\\n002| \\\"database\\\": {\\n003| \\\"host\\\": \\\"db.prod.internal\\\",\\n004| \\\"port\\\": 5433,\\n005| \\\"credentials\\\": {\\n006| \\\"username\\\": \\\"admin\\\",\\n007| \\\"password\\\": \\\"secret\\\"\\n008| }\\n009| },\\n010| \\\"features\\\": [\\n011| \\\"auth\\\",\\n012| \\\"logging\\\",\\n013| \\\"caching\\\"\\n014| ]\\n015|}\\n\\n\\nError parsing JSON:\\n\\n---JSON---\\n{\\n \\\"database\\\": {\\n \\\"host\\\": \\\"db.prod.internal\\\",\\n \\\"port\\\": 5433,\\n \\\"credentials\\\": {\\n \\\"username\\\": \\\"admin\\\",\\n \\\"password\\\": \\\"secret\\\"\\n }\\n },\\n \\\"features\\\": [\\n \\\"auth\\\",\\n \\\"logging\\\",\\n \\\"caching\\\"\\n ]\\n}\\n\\n---Validation Errors---\\n\\n1. Field: \\\"database.port\\\"\\n Problem: Number must be at least 5434\\n Message: Number must be greater than or equal to 5434\\n\\n\\nFix the JSON to match the schema. Only change what's needed to satisfy the validation errors above.\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.patch\",\"promptSource\":\"zai:zai.patch:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"\nā¼ļø=004| \"port\": 5434,\n","metadata":{"provider":"cerebras","usage":{"inputTokens":1334,"outputTokens":209,"inputCost":0.0004669,"outputCost":0.00015675},"model":"cerebras:gpt-oss-120b","ttft":174,"latency":332,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.00062365,"requestId":"req-006f8b68-7fad-42da-9e55-e9a74b88a7a2"}}}
+{"key":"98ead616","input":"{\"body\":{\"messages\":[{\"content\":\"You are a code patching assistant. Your task is to generate precise line-based patches using the Micropatch protocol.\\n\\n## Input Format\\n\\nYou will receive files in this XML format:\\n\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n\\n\\n\\n001|export function add(a, b) {\\n002| return a + b\\n003|}\\n\\n```\\n\\nEach file has:\\n- **path**: Full file path\\n- **name**: File name\\n- **Numbered lines**: Format is `NNN|content` where NNN is the ORIGINAL line number (1-based)\\n\\n## Output Format\\n\\nGenerate patches for EACH file that needs modification using this EXACT XML format:\\n\\n```\\n\\nā¼ļø=1|const a = 1\\nā¼ļø=2|const b = 2\\nā¼ļø=3|console.log(a + b)\\n\\n\\n\\nā¼ļø<1|/**\\n * Adds two numbers\\n */\\n\\n```\\n\\n**CRITICAL RULES**:\\n1. Each `` tag MUST include the exact `path` attribute from the input\\n2. Put patch operations for EACH file inside its own `...` block\\n3. If a file doesn't need changes, omit its `` block entirely\\n4. DO NOT mix patches from different files\\n5. DO NOT include line numbers or any text outside the patch operations\\n\\n## Micropatch Protocol\\n\\nThe Micropatch protocol uses line numbers to reference ORIGINAL lines (before any edits).\\n\\n### Operations\\n\\nEach operation starts with the marker `ā¼ļø` at the beginning of a line:\\n\\n1. **Insert BEFORE line**: `ā¼ļøNNN|text`\\n - Inserts `text` as a new line AFTER original line NNN\\n - Example: `ā¼ļø>10|}`\\n\\n3. **Replace single line**: `ā¼ļø=NNN|new text`\\n - Replaces original line NNN with `new text`\\n - Can span multiple lines (continue until next ā¼ļø or end)\\n - Example:\\n ```\\n ā¼ļø=7|function newName() {\\n return 42\\n }\\n ```\\n\\n4. **Replace range**: `ā¼ļø=NNN-MMM|replacement`\\n - Replaces lines NNN through MMM with replacement text\\n - Example: `ā¼ļø=5-8|const combined = a + b + c + d`\\n\\n5. **Delete single line**: `ā¼ļø-NNN`\\n - Deletes original line NNN\\n - Example: `ā¼ļø-12`\\n\\n6. **Delete range**: `ā¼ļø-NNN-MMM`\\n - Deletes lines NNN through MMM inclusive\\n - Example: `ā¼ļø-5-10`\\n\\n### Escaping\\n\\n- To include a literal `ā¼ļø` in your text, use `\\\\ā¼ļø`\\n- No other escape sequences are recognized\\n\\n### Important Rules\\n\\n1. **Use ORIGINAL line numbers**: Always reference the line numbers shown in the input (001, 002, etc.)\\n2. **One operation per line**: Each operation must start on a new line with `ā¼ļø`\\n3. **No explanations**: Output ONLY patch operations inside `` tags\\n4. **Precise operations**: Use the minimal set of operations to achieve the goal\\n5. **Verify line numbers**: Double-check that line numbers match the input\\n\\n## Example\\n\\n**Input:**\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n004|\\n005|export { x, y }\\n\\n```\\n\\n**Task:** Change variable names from x,y to a,b\\n\\n**Output:**\\n```\\n\\nā¼ļø=1|const a = 1\\nā¼ļø=2|const b = 2\\nā¼ļø=3|console.log(a + b)\\nā¼ļø=5|export { a, b }\\n\\n```\\n\\n## Your Task\\n\\nGenerate ONLY the `` blocks with patch operations. Do not include explanations, comments, or any other text.\",\"role\":\"system\"},{\"content\":\"Instructions: add a new row to the table for \\\"Grape\\\" with color \\\"Purple\\\" and price \\\"2.00\\\"\\n\\n\\n001|# Fruits\\n002|\\n003|| Name | Color | Price |\\n004|| ------ | ------ | ----- |\\n005|| Apple | Red | 1.00 |\\n006|| Banana | Yellow | 0.50 |\\n\\n\\nGenerate patches for each file that needs modification:\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.patch\",\"promptSource\":\"zai:zai.patch:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"\nā¼ļø>006||| Grape | Purple | 2.00 |\n","metadata":{"provider":"cerebras","usage":{"inputTokens":1180,"outputTokens":388,"inputCost":0.000413,"outputCost":0.000291},"model":"cerebras:gpt-oss-120b","ttft":71,"latency":0,"cached":true,"fallbackPath":[],"stopReason":"stop","cost":0.000704,"requestId":"req-a50d96a9-5579-40e4-b0da-f8531c3165db"}}}
+{"key":"b99b1256","input":"{\"body\":{\"messages\":[{\"content\":\"You are a code patching assistant. Your task is to generate precise line-based patches using the Micropatch protocol.\\n\\n## Input Format\\n\\nYou will receive files in this XML format:\\n\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n\\n\\n\\n001|export function add(a, b) {\\n002| return a + b\\n003|}\\n\\n```\\n\\nEach file has:\\n- **path**: Full file path\\n- **name**: File name\\n- **Numbered lines**: Format is `NNN|content` where NNN is the ORIGINAL line number (1-based)\\n\\n## Output Format\\n\\nGenerate patches for EACH file that needs modification using this EXACT XML format:\\n\\n```\\n\\nā¼ļø=1|const a = 1\\nā¼ļø=2|const b = 2\\nā¼ļø=3|console.log(a + b)\\n\\n\\n\\nā¼ļø<1|/**\\n * Adds two numbers\\n */\\n\\n```\\n\\n**CRITICAL RULES**:\\n1. Each `` tag MUST include the exact `path` attribute from the input\\n2. Put patch operations for EACH file inside its own `...` block\\n3. If a file doesn't need changes, omit its `` block entirely\\n4. DO NOT mix patches from different files\\n5. DO NOT include line numbers or any text outside the patch operations\\n\\n## Micropatch Protocol\\n\\nThe Micropatch protocol uses line numbers to reference ORIGINAL lines (before any edits).\\n\\n### Operations\\n\\nEach operation starts with the marker `ā¼ļø` at the beginning of a line:\\n\\n1. **Insert BEFORE line**: `ā¼ļøNNN|text`\\n - Inserts `text` as a new line AFTER original line NNN\\n - Example: `ā¼ļø>10|}`\\n\\n3. **Replace single line**: `ā¼ļø=NNN|new text`\\n - Replaces original line NNN with `new text`\\n - Can span multiple lines (continue until next ā¼ļø or end)\\n - Example:\\n ```\\n ā¼ļø=7|function newName() {\\n return 42\\n }\\n ```\\n\\n4. **Replace range**: `ā¼ļø=NNN-MMM|replacement`\\n - Replaces lines NNN through MMM with replacement text\\n - Example: `ā¼ļø=5-8|const combined = a + b + c + d`\\n\\n5. **Delete single line**: `ā¼ļø-NNN`\\n - Deletes original line NNN\\n - Example: `ā¼ļø-12`\\n\\n6. **Delete range**: `ā¼ļø-NNN-MMM`\\n - Deletes lines NNN through MMM inclusive\\n - Example: `ā¼ļø-5-10`\\n\\n### Escaping\\n\\n- To include a literal `ā¼ļø` in your text, use `\\\\ā¼ļø`\\n- No other escape sequences are recognized\\n\\n### Important Rules\\n\\n1. **Use ORIGINAL line numbers**: Always reference the line numbers shown in the input (001, 002, etc.)\\n2. **One operation per line**: Each operation must start on a new line with `ā¼ļø`\\n3. **No explanations**: Output ONLY patch operations inside `` tags\\n4. **Precise operations**: Use the minimal set of operations to achieve the goal\\n5. **Verify line numbers**: Double-check that line numbers match the input\\n\\n## Example\\n\\n**Input:**\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n004|\\n005|export { x, y }\\n\\n```\\n\\n**Task:** Change variable names from x,y to a,b\\n\\n**Output:**\\n```\\n\\nā¼ļø=1|const a = 1\\nā¼ļø=2|const b = 2\\nā¼ļø=3|console.log(a + b)\\nā¼ļø=5|export { a, b }\\n\\n```\\n\\n## Your Task\\n\\nGenerate ONLY the `` blocks with patch operations. Do not include explanations, comments, or any other text.\",\"role\":\"system\"},{\"content\":\"Instructions: change the price of Apple from 1.00 to 1.50\\n\\n\\n001|# Fruits\\n002|\\n003|| Name | Color | Price |\\n004|| ------ | ------ | ----- |\\n005|| Apple | Red | 1.00 |\\n006|| Banana | Yellow | 0.50 |\\n\\n\\nGenerate patches for each file that needs modification:\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.patch\",\"promptSource\":\"zai:zai.patch:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"\nā¼ļø=005||| Apple | Red | 1.50 |\n","metadata":{"provider":"cerebras","usage":{"inputTokens":1172,"outputTokens":288,"inputCost":0.0004102,"outputCost":0.000216},"model":"cerebras:gpt-oss-120b","ttft":86,"latency":0,"cached":true,"fallbackPath":[],"stopReason":"stop","cost":0.0006262,"requestId":"req-43ade5ec-5264-464a-b24a-a4ac4a9e9ff2"}}}
diff --git a/packages/zai/e2e/patch.test.ts b/packages/zai/e2e/patch.test.ts
index d844ad7889f..9010c2a43fb 100644
--- a/packages/zai/e2e/patch.test.ts
+++ b/packages/zai/e2e/patch.test.ts
@@ -236,6 +236,76 @@ describe('zai.patch', { timeout: 60_000 }, () => {
expect(result.output[0].patch).toMatchInlineSnapshot(`"ā¼ļø>3|## Installation"`)
})
+ it('patches a markdown table by adding a row', async () => {
+ const tableFile: File = {
+ path: 'fruits.md',
+ name: 'fruits.md',
+ content: `# Fruits
+
+| Name | Color | Price |
+| ------ | ------ | ----- |
+| Apple | Red | 1.00 |
+| Banana | Yellow | 0.50 |`,
+ }
+
+ const result = await zai
+ .patch([tableFile], 'add a new row to the table for "Grape" with color "Purple" and price "2.00"')
+ .result()
+
+ expect(result.output).toHaveLength(1)
+ const patched = result.output[0].content
+
+ // Grape row exists and is well-formed (no doubled pipes like "||")
+ expect(patched).toContain('Grape')
+ expect(patched).toContain('Purple')
+ expect(patched).toContain('2.00')
+ expect(patched).not.toMatch(/\|\|/)
+
+ // Existing rows are preserved
+ expect(patched).toContain('| Apple | Red | 1.00 |')
+ expect(patched).toContain('| Banana | Yellow | 0.50 |')
+
+ // Every table row starts with a single pipe (not a double pipe)
+ const tableRows = patched.split('\n').filter((l) => l.trim().startsWith('|'))
+ for (const row of tableRows) {
+ expect(row.trimStart().startsWith('||')).toBe(false)
+ }
+ })
+
+ it('patches a markdown table by changing an existing row', async () => {
+ const tableFile: File = {
+ path: 'fruits.md',
+ name: 'fruits.md',
+ content: `# Fruits
+
+| Name | Color | Price |
+| ------ | ------ | ----- |
+| Apple | Red | 1.00 |
+| Banana | Yellow | 0.50 |`,
+ }
+
+ const result = await zai.patch([tableFile], 'change the price of Apple from 1.00 to 1.50').result()
+
+ expect(result.output).toHaveLength(1)
+ const patched = result.output[0].content
+
+ expect(patched).toContain('1.50')
+ expect(patched).not.toContain('| Apple | Red | 1.00 |')
+
+ // No doubled pipes ā guards against a bug where lines starting with `|`
+ // get an extra leading pipe added by the patch protocol.
+ expect(patched).not.toMatch(/\|\|/)
+
+ // Banana row is untouched
+ expect(patched).toContain('| Banana | Yellow | 0.50 |')
+
+ // Every table row starts with a single pipe
+ const tableRows = patched.split('\n').filter((l) => l.trim().startsWith('|'))
+ for (const row of tableRows) {
+ expect(row.trimStart().startsWith('||')).toBe(false)
+ }
+ })
+
it('can add multiple sections to different files', async () => {
const file1: File = { path: 'file1.txt', name: 'file1.txt', content: 'Line 1\nLine 2' }
const file2: File = { path: 'file2.txt', name: 'file2.txt', content: 'Content A\nContent B' }
diff --git a/packages/zai/package.json b/packages/zai/package.json
index 8ad8144288c..160fc166187 100644
--- a/packages/zai/package.json
+++ b/packages/zai/package.json
@@ -1,7 +1,7 @@
{
"name": "@botpress/zai",
"description": "Zui AI (zai) ā An LLM utility library written on top of Zui and the Botpress API",
- "version": "2.6.21",
+ "version": "2.6.22",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
@@ -56,7 +56,7 @@
},
"peerDependencies": {
"@bpinternal/thicktoken": "^1.0.0",
- "@bpinternal/zui": "^2.1.1"
+ "@bpinternal/zui": "^2.2.1"
},
"engines": {
"node": ">=18.0.0"
diff --git a/packages/zai/src/micropatch.ts b/packages/zai/src/micropatch.ts
index f4f5e2d1ad7..f937fda0c70 100644
--- a/packages/zai/src/micropatch.ts
+++ b/packages/zai/src/micropatch.ts
@@ -153,7 +153,10 @@ export class Micropatch {
const op = m[1] as '<' | '>' | '=' | '-'
const aNum = parseInt(m[2], 10)
const bNum = m[3] ? parseInt(m[3], 10) : undefined
- const firstPayload = m[4] ?? ''
+ // LLMs sometimes "double" the leading `|` of a payload that itself begins with `|`
+ // (e.g. markdown table rows), as if escaping it. The protocol has no such escape,
+ // so a payload starting with `||` is treated as a single literal leading `|`.
+ const firstPayload = (m[4] ?? '').replace(/^\|\|/, '|')
if (aNum < 1 || (bNum !== undefined && bNum < aNum)) {
throw new Error(`Invalid line/range at line ${i + 1}: ${line}`)
diff --git a/packages/zui/src/transforms/common/errors.ts b/packages/zui/src/transforms/common/errors.ts
index 4306dab172b..3551001e3db 100644
--- a/packages/zui/src/transforms/common/errors.ts
+++ b/packages/zui/src/transforms/common/errors.ts
@@ -21,33 +21,33 @@ export abstract class ZuiTransformError extends Error {
// json-schema-to-zui-error
export class JSONSchemaToZuiError extends ZuiTransformError {
- public constructor(message?: string, path?: string) {
- super('json-schema-to-zui', message, path)
+ public constructor(message?: string) {
+ super('json-schema-to-zui', message)
}
}
export class UnsupportedJSONSchemaToZuiError extends JSONSchemaToZuiError {
- public constructor(schema: JSONSchema7, path?: string) {
- super(`JSON Schema ${JSON.stringify(schema)} cannot be transformed to ZUI type.`, path)
+ public constructor(schema: JSONSchema7) {
+ super(`JSON Schema ${JSON.stringify(schema)} cannot be transformed to ZUI type.`)
}
}
// object-to-zui-error
export class ObjectToZuiError extends ZuiTransformError {
- public constructor(message?: string, path?: string) {
- super('object-to-zui', message, path)
+ public constructor(message?: string) {
+ super('object-to-zui', message)
}
}
// zui-to-json-schema-error
export class ZuiToJSONSchemaError extends ZuiTransformError {
- public constructor(message?: string, path?: string) {
+ public constructor(message: string, path: string) {
super('zui-to-json-schema', message, path)
}
}
export class UnsupportedZuiToJSONSchemaError extends ZuiToJSONSchemaError {
public constructor(
type: ZodNativeTypeName,
- path?: string,
+ path: string,
{ suggestedAlternative }: { suggestedAlternative?: string } = {}
) {
const msg = suggestedAlternative
@@ -57,19 +57,19 @@ export class UnsupportedZuiToJSONSchemaError extends ZuiToJSONSchemaError {
}
}
export class UnsupportedZuiCheckToJSONSchemaError extends ZuiToJSONSchemaError {
- public constructor(zodType: ZodNativeTypeName, checkKind: string, path?: string) {
+ public constructor(zodType: ZodNativeTypeName, checkKind: string, path: string) {
super(`Zod check .${checkKind}() of type ${zodType} cannot be transformed to JSON Schema.`, path)
}
}
// zui-to-typescript-schema-error
export class ZuiToTypescriptSchemaError extends ZuiTransformError {
- public constructor(message?: string, path?: string) {
+ public constructor(message: string, path: string) {
super('zui-to-typescript-schema', message, path)
}
}
export class UnsupportedZuiToTypescriptSchemaError extends ZuiToTypescriptSchemaError {
- public constructor(type: ZodNativeTypeName, path?: string) {
+ public constructor(type: ZodNativeTypeName, path: string) {
super(`Zod type ${type} cannot be transformed to TypeScript schema.`, path)
}
}
@@ -81,17 +81,17 @@ export class ZuiToTypescriptTypeError extends ZuiTransformError {
}
}
export class UnsupportedZuiToTypescriptTypeError extends ZuiToTypescriptTypeError {
- public constructor(type: ZodNativeTypeName, path?: string) {
+ public constructor(type: ZodNativeTypeName, path: string) {
super(`Zod type ${type} cannot be transformed to TypeScript type.`, path)
}
}
export class UntitledDeclarationError extends ZuiToTypescriptTypeError {
- public constructor(path?: string) {
- super('Schema must have a title to be transformed to a TypeScript type with a declaration.', path)
+ public constructor() {
+ super('Schema must have a title to be transformed to a TypeScript type with a declaration.')
}
}
export class UnrepresentableGenericError extends ZuiToTypescriptTypeError {
- public constructor(path?: string) {
- super('ZodRef can only be transformed to a TypeScript type with a "type" declaration.', path)
+ public constructor() {
+ super('ZodRef can only be transformed to a TypeScript type with a "type" declaration.')
}
}
diff --git a/packages/zui/src/transforms/zui-to-json-schema/index.test.ts b/packages/zui/src/transforms/zui-to-json-schema/index.test.ts
index dab73a9497c..0105f4d63ba 100644
--- a/packages/zui/src/transforms/zui-to-json-schema/index.test.ts
+++ b/packages/zui/src/transforms/zui-to-json-schema/index.test.ts
@@ -415,22 +415,133 @@ describe('zuiToJSONSchemaNext', () => {
expect(schema).toEqual({ $ref: 'foo' })
})
- test('should show complete path section in error message', () => {
+ test('should add object keys to path', () => {
try {
- toJSONSchema(z.object({ foo: z.object({ bar: z.tuple([z.number(), z.void()]) }) }))
+ toJSONSchema(z.object({ foo: z.object({ bar: z.void() }) }))
expect.fail('should have thrown')
} catch (e) {
- expect(e instanceof Error && e.message).toContain('#.foo.bar[1]')
+ expect(e).toBeInstanceOf(errs.ZuiTransformError)
+ expect((e as errs.ZuiTransformError).path).toBe('#.foo.bar')
+ }
+ })
+
+ test('should add [number] section to array types', () => {
+ try {
+ toJSONSchema(z.array(z.void()))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errs.ZuiTransformError)
+ expect((e as errs.ZuiTransformError).path).toBe('#[number]')
+ }
+ })
+
+ test('should add [index] section to tuple types', () => {
+ try {
+ toJSONSchema(z.tuple([z.number(), z.void()]))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errs.ZuiTransformError)
+ expect((e as errs.ZuiTransformError).path).toBe('#[1]')
+ }
+ })
+
+ test('should add [number] section to tuple rest', () => {
+ try {
+ toJSONSchema(z.tuple([z.number(), z.string()]).rest(z.string().refine((v) => v.length > 0)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errs.ZuiTransformError)
+ expect((e as errs.ZuiTransformError).path).toBe('#[number]')
+ }
+ })
+
+ test('should add [number] section to set types', () => {
+ try {
+ toJSONSchema(z.set(z.void()))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errs.ZuiTransformError)
+ expect((e as errs.ZuiTransformError).path).toBe('#[number]')
+ }
+ })
+
+ test('should add [string] section to record types with string key', () => {
+ try {
+ toJSONSchema(z.record(z.string(), z.void()))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errs.ZuiTransformError)
+ expect((e as errs.ZuiTransformError).path).toBe('#[string]')
+ }
+ })
+
+ test('should add [number] section to record types with number key', () => {
+ try {
+ toJSONSchema(z.record(z.number(), z.void()))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errs.ZuiTransformError)
+ expect((e as errs.ZuiTransformError).path).toBe('#[number]')
+ }
+ })
+
+ test('should add [*] section to record types with any key', () => {
+ try {
+ toJSONSchema(z.record(z.boolean(), z.void()))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errs.ZuiTransformError)
+ expect((e as errs.ZuiTransformError).path).toBe('#[*]')
+ }
+ })
+
+ test('should add [index] section to union types', () => {
+ try {
+ toJSONSchema(z.union([z.boolean(), z.void()]))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errs.ZuiTransformError)
+ expect((e as errs.ZuiTransformError).path).toBe('#[1]')
+ }
+ })
+
+ test('should add [index] section to discriminated union types', () => {
+ try {
+ toJSONSchema(
+ z.discriminatedUnion('type', [
+ z.object({ type: z.literal('a'), foo: z.string() }),
+ z.object({ type: z.literal('b'), bar: z.string().refine((v) => v.length > 0) }),
+ ])
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errs.ZuiTransformError)
+ expect((e as errs.ZuiTransformError).path).toBe('#[1].bar')
+ }
+ })
+
+ test('should add [index] section to intersection types', () => {
+ try {
+ toJSONSchema(
+ z.intersection(
+ z.string().refine((v) => v.length > 0),
+ z.string()
+ )
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errs.ZuiTransformError)
+ expect((e as errs.ZuiTransformError).path).toBe('#[0]')
}
})
- test('should expose path as a property on the error', () => {
+ test('should add [string] section to additional properties', () => {
try {
- toJSONSchema(z.object({ foo: z.object({ bar: z.tuple([z.number(), z.void()]) }) }))
+ toJSONSchema(z.object({}).catchall(z.void()))
expect.fail('should have thrown')
} catch (e) {
expect(e).toBeInstanceOf(errs.ZuiTransformError)
- expect((e as errs.ZuiTransformError).path).toBe('#.foo.bar[1]')
+ expect((e as errs.ZuiTransformError).path).toBe('#[string]')
}
})
})
diff --git a/packages/zui/src/transforms/zui-to-json-schema/index.ts b/packages/zui/src/transforms/zui-to-json-schema/index.ts
index 9d266b660b5..d656632292f 100644
--- a/packages/zui/src/transforms/zui-to-json-schema/index.ts
+++ b/packages/zui/src/transforms/zui-to-json-schema/index.ts
@@ -127,7 +127,8 @@ function _toJSONSchema(
const properties = shape
.map(([key, value]) => [key, value.mandatory()] satisfies [string, z.ZodType])
.map(
- ([key, value]) => [key, _toJSONSchema(value, opts, path.appendSection(key))] satisfies [string, json.Schema]
+ ([key, value]) =>
+ [key, _toJSONSchema(value, opts, path.withIndexType('key', key))] satisfies [string, json.Schema]
)
return {
diff --git a/packages/zui/src/transforms/zui-to-typescript-schema/index.test.ts b/packages/zui/src/transforms/zui-to-typescript-schema/index.test.ts
index fce50835cb6..ef8cd1d8b14 100644
--- a/packages/zui/src/transforms/zui-to-typescript-schema/index.test.ts
+++ b/packages/zui/src/transforms/zui-to-typescript-schema/index.test.ts
@@ -692,4 +692,239 @@ describe.concurrent('toTypescriptSchema', () => {
expect(evaluated.getMetadata().patate).toBe('pilƩe')
})
})
+
+ test('should add object keys to path', () => {
+ try {
+ toTypescript(z.object({ foo: z.object({ bar: z.string().refine((v) => v.length > 0) }) }))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#.foo.bar')
+ }
+ })
+
+ test('should add [number] section to array types', () => {
+ try {
+ toTypescript(z.array(z.string().refine((v) => v.length > 0)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[number]')
+ }
+ })
+
+ test('should add [index] section to tuple types', () => {
+ try {
+ toTypescript(z.tuple([z.number(), z.string().refine((v) => v.length > 0)]))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[1]')
+ }
+ })
+
+ test('should add [number] section to set types', () => {
+ try {
+ toTypescript(z.set(z.string().refine((v) => v.length > 0)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[number]')
+ }
+ })
+
+ test('should add keyOf wrapper to record types with an invalid key', () => {
+ try {
+ toTypescript(
+ z.record(
+ z.string().refine((v) => v.length > 0),
+ z.string()
+ )
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('KeyOf<#>')
+ }
+ })
+
+ test('should add [string] section to record types with string key', () => {
+ try {
+ toTypescript(
+ z.record(
+ z.string(),
+ z.string().refine((v) => v.length > 0)
+ )
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[string]')
+ }
+ })
+
+ test('should add [number] section to record types with number key', () => {
+ try {
+ toTypescript(
+ z.record(
+ z.number(),
+ z.string().refine((v) => v.length > 0)
+ )
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[number]')
+ }
+ })
+
+ test('should add [*] section to record types with any key', () => {
+ try {
+ toTypescript(
+ z.record(
+ z.boolean(),
+ z.string().refine((v) => v.length > 0)
+ )
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[*]')
+ }
+ })
+
+ test('should add keyOf wrapper to map types with an invalid key', () => {
+ try {
+ toTypescript(
+ z.map(
+ z.string().refine((v) => v.length > 0),
+ z.string()
+ )
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('KeyOf<#>')
+ }
+ })
+
+ test('should add [string] section to map types with string key', () => {
+ try {
+ toTypescript(
+ z.map(
+ z.string(),
+ z.string().refine((v) => v.length > 0)
+ )
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[string]')
+ }
+ })
+
+ test('should add [number] section to map types with number key', () => {
+ try {
+ toTypescript(
+ z.map(
+ z.number(),
+ z.string().refine((v) => v.length > 0)
+ )
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[number]')
+ }
+ })
+
+ test('should add [*] section to map types with any key', () => {
+ try {
+ toTypescript(
+ z.map(
+ z.boolean(),
+ z.string().refine((v) => v.length > 0)
+ )
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[*]')
+ }
+ })
+
+ test('should add [index] section to union types', () => {
+ try {
+ toTypescript(z.union([z.boolean(), z.string().refine((v) => v.length > 0)]))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[1]')
+ }
+ })
+
+ test('should add [index] section to discriminated union types', () => {
+ try {
+ toTypescript(
+ z.discriminatedUnion('type', [
+ z.object({ type: z.literal('a'), foo: z.string() }),
+ z.object({ type: z.literal('b'), bar: z.number().refine((v) => v > 0) }),
+ ])
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[1].bar')
+ }
+ })
+
+ test('should add [index] section to intersection types', () => {
+ try {
+ toTypescript(
+ z.intersection(
+ z.string().refine((v) => v.length > 0),
+ z.number()
+ )
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[0]')
+ }
+ })
+
+ test('should add parameters wrapper to function type and show index', () => {
+ try {
+ toTypescript(z.function(z.tuple([z.boolean(), z.string().refine((v) => v.length > 0)]), z.string()))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('Parameters<#>[1]')
+ }
+ })
+
+ test('should add returnType wrapper to function return type invalid', () => {
+ try {
+ toTypescript(
+ z.function(
+ z.tuple([z.boolean(), z.number()]),
+ z.string().refine((v) => v.length > 0)
+ )
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('ReturnType<#>')
+ }
+ })
+
+ test('should add [string] section to additional properties', () => {
+ try {
+ toTypescript(z.object({}).catchall(z.string().refine((v) => v.length > 0)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[string]')
+ }
+ })
})
diff --git a/packages/zui/src/transforms/zui-to-typescript-schema/index.ts b/packages/zui/src/transforms/zui-to-typescript-schema/index.ts
index 446e5cd1b96..32149ba67e5 100644
--- a/packages/zui/src/transforms/zui-to-typescript-schema/index.ts
+++ b/packages/zui/src/transforms/zui-to-typescript-schema/index.ts
@@ -1,6 +1,7 @@
-import { mapValues, isEqual } from 'lodash-es'
+import { isEqual } from 'lodash-es'
import * as utils from '../../utils'
+import { PropertyPath } from '../../utils/property-path-utils'
import * as z from '../../z'
import * as errors from '../common/errors'
import {
@@ -24,12 +25,16 @@ const { zuiKey } = z
* @returns a typescript program that would construct the given schema if executed
*/
export function toTypescriptSchema(schema: z.ZodType): string {
+ return _toTypescriptSchema(schema, new PropertyPath())
+}
+
+function _toTypescriptSchema(schema: z.ZodType, path: PropertyPath): string {
const wrappedSchema: z.ZodType = schema
- const dts = sUnwrapZod(wrappedSchema)
+ const dts = sUnwrapZod(wrappedSchema, path)
return dts
}
-function sUnwrapZod(schema: z.ZodType): string {
+function sUnwrapZod(schema: z.ZodType, path: PropertyPath): string {
const s = schema as z.ZodNativeType
switch (s.typeName) {
case 'ZodString':
@@ -69,12 +74,15 @@ function sUnwrapZod(schema: z.ZodType): string {
return `z.void()${_addMetadata(s._def)}`.trim()
case 'ZodArray':
- return `z.array(${sUnwrapZod(s._def.type)})${generateArrayChecks(s._def)}${_addMetadata(s._def, s._def.type)}`
+ return `z.array(${sUnwrapZod(s._def.type, path.withIndexType('number'))})${generateArrayChecks(s._def)}${_addMetadata(s._def, s._def.type)}`
case 'ZodObject':
- const props = mapValues(s.shape, sUnwrapZod)
+ const props: Record = {}
+ for (const [key, value] of Object.entries(s.shape)) {
+ props[key] = sUnwrapZod(value, path.withIndexType('key', key))
+ }
const catchall = s.additionalProperties()
- const catchallString = catchall ? `.catchall(${sUnwrapZod(catchall)})` : ''
+ const catchallString = catchall ? `.catchall(${sUnwrapZod(catchall, path.withIndexType('string'))})` : ''
return [
//
'z.object({',
@@ -85,44 +93,56 @@ function sUnwrapZod(schema: z.ZodType): string {
.trim()
case 'ZodUnion':
- const options = s._def.options.map(sUnwrapZod)
+ const options = s._def.options.map((option, index) => sUnwrapZod(option, path.withIndexType('number', index)))
return `z.union([${options.join(', ')}])${_addMetadata(s._def)}`.trim()
case 'ZodDiscriminatedUnion':
- const opts = s._def.options.map(sUnwrapZod)
+ const opts = s._def.options.map((option, index) => sUnwrapZod(option, path.withIndexType('number', index)))
const discriminator = primitiveToTypescriptValue(s._def.discriminator)
return `z.discriminatedUnion(${discriminator}, [${opts.join(', ')}])${_addMetadata(s._def)}`.trim()
case 'ZodIntersection':
- const left: string = sUnwrapZod(s._def.left)
- const right: string = sUnwrapZod(s._def.right)
+ const left: string = sUnwrapZod(s._def.left, path.withIndexType('number', 0))
+ const right: string = sUnwrapZod(s._def.right, path.withIndexType('number', 1))
return `z.intersection(${left}, ${right})${_addMetadata(s._def)}`.trim()
case 'ZodTuple':
- const items = s._def.items.map(sUnwrapZod)
+ const items = s._def.items.map((item, index) => sUnwrapZod(item, path.withIndexType('number', index)))
return `z.tuple([${items.join(', ')}])${_addMetadata(s._def)}`.trim()
case 'ZodRecord':
- const keyType = sUnwrapZod(s._def.keyType)
- const valueType = sUnwrapZod(s._def.valueType)
+ const keyType = sUnwrapZod(s._def.keyType, path.withWrapper('KeyOf'))
+ const recordPath = z.is.zuiString(s._def.keyType)
+ ? path.withIndexType('string')
+ : z.is.zuiNumber(s._def.keyType)
+ ? path.withIndexType('number')
+ : path.withIndexType('any')
+ const valueType = sUnwrapZod(s._def.valueType, recordPath)
return `z.record(${keyType}, ${valueType})${_addMetadata(s._def)}`.trim()
case 'ZodMap':
- const mapKeyType = sUnwrapZod(s._def.keyType)
- const mapValueType = sUnwrapZod(s._def.valueType)
+ const mapKeyType = sUnwrapZod(s._def.keyType, path.withWrapper('KeyOf'))
+ const mapPath = z.is.zuiString(s._def.keyType)
+ ? path.withIndexType('string')
+ : z.is.zuiNumber(s._def.keyType)
+ ? path.withIndexType('number')
+ : path.withIndexType('any')
+ const mapValueType = sUnwrapZod(s._def.valueType, mapPath)
return `z.map(${mapKeyType}, ${mapValueType})${_addMetadata(s._def)}`.trim()
case 'ZodSet':
- return `z.set(${sUnwrapZod(s._def.valueType)})${generateSetChecks(s._def)}${_addMetadata(s._def)}`.trim()
+ return `z.set(${sUnwrapZod(s._def.valueType, path.withIndexType('number'))})${generateSetChecks(s._def)}${_addMetadata(s._def)}`.trim()
case 'ZodFunction':
- const args = s._def.args.items.map(sUnwrapZod)
+ const args = s._def.args.items.map((arg, index) =>
+ sUnwrapZod(arg, path.withWrapper('Parameters').withIndexType('number', index))
+ )
const argsString = args.length ? `.args(${args.join(', ')})` : ''
- const returns = sUnwrapZod(s._def.returns)
+ const returns = sUnwrapZod(s._def.returns, path.withWrapper('ReturnType'))
return `z.function()${argsString}.returns(${returns})${_addMetadata(s._def)}`.trim()
case 'ZodLazy':
- return `z.lazy(() => ${sUnwrapZod(s._def.getter())})${_addMetadata(s._def)}`.trim()
+ return `z.lazy(() => ${sUnwrapZod(s._def.getter(), path)})${_addMetadata(s._def)}`.trim()
case 'ZodLiteral':
const value = primitiveToTypescriptValue(s._def.value)
@@ -133,38 +153,38 @@ function sUnwrapZod(schema: z.ZodType): string {
return `z.enum([${values.join(', ')}])${_addMetadata(s._def)}`.trim()
case 'ZodEffects':
- throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodEffects')
+ throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodEffects', path.toString())
case 'ZodNativeEnum':
- throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodNativeEnum')
+ throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodNativeEnum', path.toString())
case 'ZodOptional':
- return `z.optional(${sUnwrapZod(s._def.innerType)})${_addMetadata(s._def, s._def.innerType)}`.trim()
+ return `z.optional(${sUnwrapZod(s._def.innerType, path)})${_addMetadata(s._def, s._def.innerType)}`.trim()
case 'ZodNullable':
- return `z.nullable(${sUnwrapZod(s._def.innerType)})${_addMetadata(s._def, s._def.innerType)}`.trim()
+ return `z.nullable(${sUnwrapZod(s._def.innerType, path)})${_addMetadata(s._def, s._def.innerType)}`.trim()
case 'ZodDefault':
const defaultValue = unknownToTypescriptValue(s._def.defaultValue())
- return `z.default(${sUnwrapZod(s._def.innerType)}, ${defaultValue})${_addMetadata(s._def, s._def.innerType)}`.trim()
+ return `z.default(${sUnwrapZod(s._def.innerType, path)}, ${defaultValue})${_addMetadata(s._def, s._def.innerType)}`.trim()
case 'ZodCatch':
- throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodCatch')
+ throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodCatch', path.toString())
case 'ZodPromise':
- return `z.promise(${sUnwrapZod(s._def.type)})${_addMetadata(s._def, s._def.type)}`.trim()
+ return `z.promise(${sUnwrapZod(s._def.type, path)})${_addMetadata(s._def, s._def.type)}`.trim()
case 'ZodBranded':
- throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodBranded')
+ throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodBranded', path.toString())
case 'ZodPipeline':
- throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodPipeline')
+ throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodPipeline', path.toString())
case 'ZodSymbol':
- throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodSymbol')
+ throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodSymbol', path.toString())
case 'ZodReadonly':
- return `z.readonly(${sUnwrapZod(s._def.innerType)})${_addMetadata(s._def, s._def.innerType)}`.trim()
+ return `z.readonly(${sUnwrapZod(s._def.innerType, path)})${_addMetadata(s._def, s._def.innerType)}`.trim()
case 'ZodRef':
const uri = primitiveToTypescriptValue(s._def.uri)
diff --git a/packages/zui/src/transforms/zui-to-typescript-type/index.test.ts b/packages/zui/src/transforms/zui-to-typescript-type/index.test.ts
index 37b26a94b23..5583329e032 100644
--- a/packages/zui/src/transforms/zui-to-typescript-type/index.test.ts
+++ b/packages/zui/src/transforms/zui-to-typescript-type/index.test.ts
@@ -4,6 +4,11 @@ import * as z from '../../z'
import * as errors from '../common/errors'
import * as assert from '../../assertions.utils.test'
+enum TestEnum {
+ A = 'A',
+ B = 'B',
+}
+
const toTypescript = (schema: z.ZodType): string => {
const hasTitle = 'title' in schema.ui
if (!hasTitle) {
@@ -1030,4 +1035,181 @@ describe.concurrent('optional', () => {
const expected = `declare const MyString: string | undefined;`
await assert.expectTypescript(typings).toMatchWithoutFormatting(expected)
})
+
+ describe('error path propagation', () => {
+ it('should add object keys to path', () => {
+ try {
+ toTypescript(z.object({ foo: z.object({ bar: z.nativeEnum(TestEnum) }) }))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#.foo.bar')
+ }
+ })
+
+ it('should add [number] section to array types', () => {
+ try {
+ toTypescript(z.array(z.nativeEnum(TestEnum)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[number]')
+ }
+ })
+
+ it('should add [index] section to tuple types', () => {
+ try {
+ toTypescript(z.tuple([z.number(), z.nativeEnum(TestEnum)]))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[1]')
+ }
+ })
+
+ it('should add [number] section to set types', () => {
+ try {
+ toTypescript(z.set(z.nativeEnum(TestEnum)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[number]')
+ }
+ })
+
+ it('should add keyOf wrapper to record types with an invalid key', () => {
+ try {
+ toTypescript(z.record(z.nativeEnum(TestEnum), z.string()))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('KeyOf<#>')
+ }
+ })
+
+ it('should add [string] section to record types with string key', () => {
+ try {
+ toTypescript(z.record(z.string(), z.nativeEnum(TestEnum)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[string]')
+ }
+ })
+
+ it('should add [number] section to record types with number key', () => {
+ try {
+ toTypescript(z.record(z.number(), z.nativeEnum(TestEnum)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[number]')
+ }
+ })
+
+ it('should add [*] section to record types with any key', () => {
+ try {
+ toTypescript(z.record(z.boolean(), z.nativeEnum(TestEnum)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[*]')
+ }
+ })
+
+ it('should add keyOf wrapper to map types with an invalid key', () => {
+ try {
+ toTypescript(z.map(z.nativeEnum(TestEnum), z.string()))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('KeyOf<#>')
+ }
+ })
+
+ it('should add [string] section to map types with string key', () => {
+ try {
+ toTypescript(z.map(z.string(), z.nativeEnum(TestEnum)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[string]')
+ }
+ })
+
+ it('should add [number] section to map types with number key', () => {
+ try {
+ toTypescript(z.map(z.number(), z.nativeEnum(TestEnum)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[number]')
+ }
+ })
+
+ it('should add [*] section to map types with any key', () => {
+ try {
+ toTypescript(z.map(z.boolean(), z.nativeEnum(TestEnum)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[*]')
+ }
+ })
+
+ it('should add [index] section to union types', () => {
+ try {
+ toTypescript(z.union([z.boolean(), z.nativeEnum(TestEnum)]))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[1]')
+ }
+ })
+
+ it('should add [index] section to discriminated union types', () => {
+ try {
+ toTypescript(
+ z.discriminatedUnion('type', [
+ z.object({ type: z.literal('a'), foo: z.string() }),
+ z.object({ type: z.literal('b'), bar: z.nativeEnum(TestEnum) }),
+ ])
+ )
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[1].bar')
+ }
+ })
+
+ it('should add [index] section to intersection types', () => {
+ try {
+ toTypescript(z.intersection(z.object({ foo: z.nativeEnum(TestEnum) }), z.object({ bar: z.number() })))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('#[0].foo')
+ }
+ })
+
+ it('should add parameters wrapper to function type and show index', () => {
+ try {
+ toTypescript(z.function(z.tuple([z.boolean(), z.nativeEnum(TestEnum)]), z.string()))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('Parameters<#>[1]')
+ }
+ })
+
+ it('should add ReturnType wrapper to function return type', () => {
+ try {
+ toTypescript(z.function(z.tuple([z.boolean(), z.number()]), z.nativeEnum(TestEnum)))
+ expect.fail('should have thrown')
+ } catch (e) {
+ expect(e).toBeInstanceOf(errors.ZuiTransformError)
+ expect((e as errors.ZuiTransformError).path).toBe('ReturnType<#>')
+ }
+ })
+ }) // error path propagation
})
diff --git a/packages/zui/src/transforms/zui-to-typescript-type/index.ts b/packages/zui/src/transforms/zui-to-typescript-type/index.ts
index ea7fbd3f254..2a91d0b6027 100644
--- a/packages/zui/src/transforms/zui-to-typescript-type/index.ts
+++ b/packages/zui/src/transforms/zui-to-typescript-type/index.ts
@@ -1,4 +1,5 @@
import * as utils from '../../utils'
+import { PropertyPath } from '../../utils/property-path-utils'
import * as z from '../../z'
import * as errors from '../common/errors'
import {
@@ -99,9 +100,13 @@ type InternalOptions = {
* @returns a string of the TypeScript **type** representing the schema
*/
export function toTypescriptType(schema: z.ZodType, options: TypescriptGenerationOptions = {}): string {
+ return _toTypescriptType(schema, new PropertyPath(), options)
+}
+
+function _toTypescriptType(schema: z.ZodType, path: PropertyPath, options: TypescriptGenerationOptions = {}): string {
const wrappedSchema: Declaration = getDeclarationProps(schema, options)
- let dts = sUnwrapZod(wrappedSchema, options)
+ let dts = sUnwrapZod(wrappedSchema, path, options)
if (options.formatter) {
dts = options.formatter(dts)
@@ -112,7 +117,11 @@ export function toTypescriptType(schema: z.ZodType, options: TypescriptGeneratio
const _optionalKey = (key: string): string => (key.endsWith('?') ? key : `${key}?`)
-function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration | null, config: InternalOptions): string {
+function sUnwrapZod(
+ schema: z.ZodType | KeyValue | FnParameters | Declaration | null,
+ path: PropertyPath,
+ config: InternalOptions
+): string {
const newConfig: InternalOptions = {
...config,
declaration: false,
@@ -124,7 +133,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration |
}
if (schema instanceof Declaration) {
- return unwrapDeclaration(schema, newConfig)
+ return unwrapDeclaration(schema, path, newConfig)
}
if (schema instanceof KeyValue) {
@@ -142,7 +151,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration |
innerType = innerType?.describe(optionalValue.description)
}
- return sUnwrapZod(new KeyValue(_optionalKey(schema.key), innerType), newConfig)
+ return sUnwrapZod(new KeyValue(_optionalKey(schema.key), innerType), path, newConfig)
}
const description = getMultilineComment(schema.value._def.description || schema.value.description)
@@ -151,7 +160,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration |
const isOptional = z.is.zuiAny(schema.value) // any is treated as optional for backwards compatibility
const key = isOptional ? _optionalKey(schema.key) : schema.key
- return `${delimiter}${description}${delimiter}${key}: ${sUnwrapZod(withoutDesc, newConfig)}${delimiter}`
+ return `${delimiter}${description}${delimiter}${key}: ${sUnwrapZod(withoutDesc, path, newConfig)}${delimiter}`
}
if (schema instanceof FnParameters) {
@@ -160,7 +169,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration |
for (let i = 0; i < schema.schema.items.length; i++) {
const argName = (schema.schema.items[i]?.ui?.title as string) ?? `arg${i}`
const item = schema.schema.items[i]!
- args += `${sUnwrapZod(new KeyValue(toPropertyKey(argName), item), newConfig)}${
+ args += `${sUnwrapZod(new KeyValue(toPropertyKey(argName), item), path.withIndexType('number', i), newConfig)}${
i < schema.schema.items.length - 1 ? ', ' : ''
} `
}
@@ -170,7 +179,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration |
const isLiteral = z.is.zuiLiteral(schema.schema.naked())
- const typings = sUnwrapZod(schema.schema, newConfig).trim()
+ const typings = sUnwrapZod(schema.schema, path, newConfig).trim()
const startsWithPairs =
(typings.startsWith('{') && typings.endsWith('}')) ||
(typings.startsWith('[') && typings.endsWith(']')) ||
@@ -188,10 +197,10 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration |
if (schema instanceof FnReturn) {
if (z.is.zuiOptional(schema.schema)) {
- return `${sUnwrapZod(schema.schema.unwrap(), newConfig)} | undefined`
+ return `${sUnwrapZod(schema.schema.unwrap(), path, newConfig)} | undefined`
}
- return sUnwrapZod(schema.schema, newConfig)
+ return sUnwrapZod(schema.schema, path, newConfig)
}
const s = schema as z.ZodFirstPartySchemaTypes
@@ -230,7 +239,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration |
return `${getMultilineComment(s.description)} void`.trim()
case 'ZodArray':
- const item = sUnwrapZod(s._def.type, newConfig)
+ const item = sUnwrapZod(s._def.type, path.withIndexType('number'), newConfig)
if (isPrimitive(item)) {
return `${item}[]`
@@ -241,7 +250,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration |
case 'ZodObject':
const props = Object.entries(s._def.shape()).map(([key, value]) => {
if (z.is.zuiType(value)) {
- return sUnwrapZod(new KeyValue(toPropertyKey(key), value), newConfig)
+ return sUnwrapZod(new KeyValue(toPropertyKey(key), value), path.withIndexType('key', key), newConfig)
}
return `${key}: unknown`
})
@@ -249,44 +258,56 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration |
return `{ ${props.join('; ')} }`
case 'ZodUnion':
- const options = s._def.options.map((option) => {
- return sUnwrapZod(option, newConfig)
+ const options = s._def.options.map((option, index) => {
+ return sUnwrapZod(option, path.withIndexType('number', index), newConfig)
})
return `${getMultilineComment(s.description)}
${options.join(' | ')}`
case 'ZodDiscriminatedUnion':
- const opts = s._def.options.map((option) => {
- return sUnwrapZod(option, newConfig)
+ const opts = s._def.options.map((option, index) => {
+ return sUnwrapZod(option, path.withIndexType('number', index), newConfig)
})
return `${getMultilineComment(s.description)}
${opts.join(' | ')}`
case 'ZodIntersection':
- return `${sUnwrapZod(s._def.left, newConfig)} & ${sUnwrapZod(s._def.right, newConfig)}`
+ return `${sUnwrapZod(s._def.left, path.withIndexType('number', 0), newConfig)} & ${sUnwrapZod(s._def.right, path.withIndexType('number', 1), newConfig)}`
case 'ZodTuple':
if (s._def.items.length === 0) {
return '[]'
}
- const items = s._def.items.map((i) => sUnwrapZod(i, newConfig))
+ const items = s._def.items.map((i, index) => sUnwrapZod(i, path.withIndexType('number', index), newConfig))
return `[${items.join(', ')}]`
- case 'ZodRecord':
- const keyType = sUnwrapZod(s._def.keyType, newConfig)
- const valueType = sUnwrapZod(s._def.valueType, newConfig)
+ case 'ZodRecord': {
+ const keyType = sUnwrapZod(s._def.keyType, path.withWrapper('KeyOf'), newConfig)
+ const recordPath = z.is.zuiString(s._def.keyType)
+ ? path.withIndexType('string')
+ : z.is.zuiNumber(s._def.keyType)
+ ? path.withIndexType('number')
+ : path.withIndexType('any')
+ const valueType = sUnwrapZod(s._def.valueType, recordPath, newConfig)
return `${getMultilineComment(s.description)} { [key: ${keyType}]: ${valueType} }`
+ }
- case 'ZodMap':
- return `Map<${sUnwrapZod(s._def.keyType, newConfig)}, ${sUnwrapZod(s._def.valueType, newConfig)}>`
+ case 'ZodMap': {
+ const recordPath = z.is.zuiString(s._def.keyType)
+ ? path.withIndexType('string')
+ : z.is.zuiNumber(s._def.keyType)
+ ? path.withIndexType('number')
+ : path.withIndexType('any')
+ return `Map<${sUnwrapZod(s._def.keyType, path.withWrapper('KeyOf'), newConfig)}, ${sUnwrapZod(s._def.valueType, recordPath, newConfig)}>`
+ }
case 'ZodSet':
- return `Set<${sUnwrapZod(s._def.valueType, newConfig)}>`
+ return `Set<${sUnwrapZod(s._def.valueType, path.withIndexType('number'), newConfig)}>`
case 'ZodFunction':
- const input = sUnwrapZod(new FnParameters(s._def.args), newConfig)
- const output = sUnwrapZod(new FnReturn(s._def.returns), newConfig)
+ const input = sUnwrapZod(new FnParameters(s._def.args), path.withWrapper('Parameters'), newConfig)
+ const output = sUnwrapZod(new FnReturn(s._def.returns), path.withWrapper('ReturnType'), newConfig)
const parentIsType = config?.parent instanceof Declaration && config?.parent.props.type === 'type'
if (config?.declaration && !parentIsType) {
@@ -298,7 +319,7 @@ ${opts.join(' | ')}`
(${input}) => ${output}`
case 'ZodLazy':
- return sUnwrapZod(s._def.getter(), newConfig)
+ return sUnwrapZod(s._def.getter(), path, newConfig)
case 'ZodLiteral':
const value: string = primitiveToTypscriptLiteralType(s._def.value)
@@ -310,38 +331,38 @@ ${value}`.trim()
return values.join(' | ')
case 'ZodEffects':
- return sUnwrapZod(s._def.schema, newConfig)
+ return sUnwrapZod(s._def.schema, path, newConfig)
case 'ZodNativeEnum':
- throw new errors.UnsupportedZuiToTypescriptTypeError('ZodNativeEnum')
+ throw new errors.UnsupportedZuiToTypescriptTypeError('ZodNativeEnum', path.toString())
case 'ZodOptional':
- return `${sUnwrapZod(s._def.innerType, newConfig)} | undefined`
+ return `${sUnwrapZod(s._def.innerType, path, newConfig)} | undefined`
case 'ZodNullable':
- return `${sUnwrapZod(s._def.innerType, newConfig)} | null`
+ return `${sUnwrapZod(s._def.innerType, path, newConfig)} | null`
case 'ZodDefault':
const defaultInnerType = config.treatDefaultAsOptional ? s._def.innerType.optional() : s._def.innerType
- return sUnwrapZod(defaultInnerType, newConfig)
+ return sUnwrapZod(defaultInnerType, path, newConfig)
case 'ZodCatch':
- return sUnwrapZod(s._def.innerType, newConfig)
+ return sUnwrapZod(s._def.innerType, path, newConfig)
case 'ZodPromise':
- return `Promise<${sUnwrapZod(s._def.type, newConfig)}>`
+ return `Promise<${sUnwrapZod(s._def.type, path, newConfig)}>`
case 'ZodBranded':
- return sUnwrapZod(s._def.type, newConfig)
+ return sUnwrapZod(s._def.type, path, newConfig)
case 'ZodPipeline':
- return sUnwrapZod(s._def.in, newConfig)
+ return sUnwrapZod(s._def.in, path, newConfig)
case 'ZodSymbol':
return `${getMultilineComment(s.description)} symbol`.trim()
case 'ZodReadonly':
- return `Readonly<${sUnwrapZod(s._def.innerType, newConfig)}>`
+ return `Readonly<${sUnwrapZod(s._def.innerType, path, newConfig)}>`
case 'ZodRef':
return toTypeArgumentName(s._def.uri)
@@ -351,15 +372,15 @@ ${value}`.trim()
}
}
-const unwrapDeclaration = (declaration: Declaration, options: InternalOptions): string => {
+const unwrapDeclaration = (declaration: Declaration, path: PropertyPath, options: InternalOptions): string => {
if (declaration.props.type === 'none') {
- return sUnwrapZod(declaration.props.schema, options)
+ return sUnwrapZod(declaration.props.schema, path, options)
}
const { schema, identifier } = declaration.props
const description = getMultilineComment(schema.description)
const withoutDesc = schema.describe('')
- const typings = sUnwrapZod(withoutDesc, { ...options, declaration: true })
+ const typings = sUnwrapZod(withoutDesc, path, { ...options, declaration: true })
const isLargeDeclaration = typings.split('\n').length >= LARGE_DECLARATION_LINES
const closingTag = isLargeDeclaration && options.includeClosingTags ? `// end of ${identifier}` : ''
diff --git a/packages/zui/src/utils/property-path-utils.test.ts b/packages/zui/src/utils/property-path-utils.test.ts
index 2730c9c3158..a9bbad53f15 100644
--- a/packages/zui/src/utils/property-path-utils.test.ts
+++ b/packages/zui/src/utils/property-path-utils.test.ts
@@ -6,86 +6,78 @@ describe.concurrent('PropertyPath', () => {
expect(new PropertyPath().toString()).toBe('#')
})
- it('renders a name-only section', () => {
- expect(new PropertyPath().appendSection('foo').toString()).toBe('#.foo')
+ it('doesnt start with # when starting with wrapper', () => {
+ expect(new PropertyPath().withIndexType('key', 'foo').withWrapper('KeyOf').toString()).not.toMatch(/^#/)
+ })
+
+ it('adds a key index with a dot', () => {
+ expect(new PropertyPath().withIndexType('key', 'foo').toString()).toBe('#.foo')
})
describe.concurrent('withIndexType', () => {
it('adds a number index without a value', () => {
- const path = new PropertyPath().appendSection('foo').withIndexType('number')
- expect(path.toString()).toBe('#.foo[number]')
+ const path = new PropertyPath().withIndexType('number')
+ expect(path.toString()).toBe('#[number]')
})
it('adds a number index with a value', () => {
- const path = new PropertyPath().appendSection('foo').withIndexType('number', 3)
- expect(path.toString()).toBe('#.foo[3]')
+ const path = new PropertyPath().withIndexType('number', 3)
+ expect(path.toString()).toBe('#[3]')
})
it('adds a string index without a value', () => {
- const path = new PropertyPath().appendSection('foo').withIndexType('string')
- expect(path.toString()).toBe('#.foo[string]')
+ const path = new PropertyPath().withIndexType('string')
+ expect(path.toString()).toBe('#[string]')
})
it('adds a string index with a value', () => {
- const path = new PropertyPath().appendSection('foo').withIndexType('string', 'foo')
- expect(path.toString()).toBe('#.foo[foo]')
+ const path = new PropertyPath().withIndexType('string', 'foo')
+ expect(path.toString()).toBe('#[foo]')
})
it('adds an any index', () => {
- const path = new PropertyPath().appendSection('foo').withIndexType('any')
- expect(path.toString()).toBe('#.foo[*]')
+ const path = new PropertyPath().withIndexType('any')
+ expect(path.toString()).toBe('#[*]')
})
- it('stacks multiple indices on the same section', () => {
- const path = new PropertyPath().appendSection('foo').withIndexType('number', 2).withIndexType('string', 'col')
- expect(path.toString()).toBe('#.foo[2][col]')
+ it('does not mutate the original', () => {
+ const original = new PropertyPath()
+ const next = original.withIndexType('key', 'foo')
+ expect(original.toString()).toBe('#')
+ expect(next.toString()).toBe('#.foo')
})
- it('applies different index types on consecutive sections', () => {
- const path = new PropertyPath()
- .appendSection('foo')
- .withIndexType('number', 2)
- .appendSection('bar')
- .withIndexType('string', 'baz')
- expect(path.toString()).toBe('#.foo[2].bar[baz]')
+ it('chains multiple indexType', () => {
+ const path = new PropertyPath().withIndexType('key', 'foo').withIndexType('key', 'bar')
+ expect(path.toString()).toBe('#.foo.bar')
})
})
- describe.concurrent('withPrefix', () => {
- it('prepends the prefix followed by a space before the #', () => {
- const path = new PropertyPath().appendSection('foo').withPrefix('keyOf')
- expect(path.toString()).toBe('keyOf #.foo')
+ describe.concurrent('withWrapper', () => {
+ it('wraps the whole path', () => {
+ const path = new PropertyPath().withIndexType('key', 'foo').withWrapper('ReturnType')
+ expect(path.toString()).toBe('ReturnType<#.foo>')
})
it('does not mutate the original', () => {
- const original = new PropertyPath().appendSection('foo')
- const prefixed: PropertyPath = original.withPrefix('keyOf')
+ const original = new PropertyPath().withIndexType('key', 'foo')
+ const wrapped: PropertyPath = original.withWrapper('ReturnType')
expect(original.toString()).toBe('#.foo')
- expect(prefixed.toString()).toBe('keyOf #.foo')
+ expect(wrapped.toString()).toBe('ReturnType<#.foo>')
})
- it('prefix is preserved through appendSection', () => {
- const path = new PropertyPath().appendSection('foo').withPrefix('keyOf').appendSection('bar')
- expect(path.toString()).toBe('keyOf #.foo.bar')
+ it('wrapper is preserved through withIndexType', () => {
+ const path = new PropertyPath().withIndexType('key', 'foo').withWrapper('ReturnType').withIndexType('key', 'bar')
+ expect(path.toString()).toBe('ReturnType<#.foo>.bar')
})
- it('prefix is preserved through withIndexType', () => {
- const path = new PropertyPath().appendSection('foo').withPrefix('keyOf').withIndexType('number', 1)
- expect(path.toString()).toBe('keyOf #.foo[1]')
- })
- })
-
- describe.concurrent('appendSection', () => {
- it('does not mutate the original', () => {
- const original = new PropertyPath()
- const next = original.appendSection('foo')
- expect(original.toString()).toBe('#')
- expect(next.toString()).toBe('#.foo')
- })
-
- it('chains multiple appends', () => {
- const path = new PropertyPath().appendSection('foo').appendSection('bar')
- expect(path.toString()).toBe('#.foo.bar')
+ it('should combine wrappers', () => {
+ const path = new PropertyPath()
+ .withIndexType('key', 'foo')
+ .withWrapper('ReturnType')
+ .withIndexType('key', 'bar')
+ .withWrapper('KeyOf')
+ expect(path.toString()).toBe('KeyOf.bar>')
})
})
})
diff --git a/packages/zui/src/utils/property-path-utils.ts b/packages/zui/src/utils/property-path-utils.ts
index 26e26352ec0..f0543278129 100644
--- a/packages/zui/src/utils/property-path-utils.ts
+++ b/packages/zui/src/utils/property-path-utils.ts
@@ -17,39 +17,42 @@ export type AnySectionIndex = {
type: 'any'
}
-export type PathSection = KeySectionIndex | NumberSectionIndex | StringSectionIndex | AnySectionIndex
+export type PathSection = KeySectionIndex | NumberSectionIndex | StringSectionIndex | AnySectionIndex | PropertyPath
export class PropertyPath {
+ public readonly type = 'propertyPath'
private readonly _sections: PathSection[]
- private readonly _prefix: string
+ private readonly _wrapper: string
- constructor(sections: PathSection[] = [], prefix: string = '') {
+ constructor(sections: PathSection[] = [], wrapper: string = '') {
this._sections = sections
- this._prefix = prefix
- }
-
- appendSection(name: string): PropertyPath {
- return new PropertyPath([...this._sections, { type: 'key', value: name }], this._prefix)
+ this._wrapper = wrapper
}
+ withIndexType(type: 'key', value: string): PropertyPath
withIndexType(type: 'number', value?: number): PropertyPath
withIndexType(type: 'string', value?: string): PropertyPath
withIndexType(type: 'any'): PropertyPath
- withIndexType(type: 'number' | 'string' | 'any', value?: number | string): PropertyPath {
- return new PropertyPath([...this._sections, { type, value } as PathSection], this._prefix)
+ withIndexType(type: 'number' | 'string' | 'any' | 'key', value?: number | string): PropertyPath {
+ return new PropertyPath([...this._sections, { type, value } as PathSection], this._wrapper)
}
- withPrefix(prefix: string): PropertyPath {
- return new PropertyPath(this._sections, prefix + ' ')
+ withWrapper(wrapper: string): PropertyPath {
+ return new PropertyPath([new PropertyPath([...this._sections], wrapper)])
}
toString(): string {
- return `${this._prefix}#${this._sections
+ const wrapperBegin = this._wrapper ? `${this._wrapper}<` : ''
+ const wrapperEnd = this._wrapper ? '>' : ''
+ const rootCarAtBeginning = this._sections.length === 0 || this._sections[0]!.type !== 'propertyPath' ? '#' : ''
+
+ return `${wrapperBegin}${rootCarAtBeginning}${this._sections
.map((section) => {
+ if (section.type === 'propertyPath') return section.toString()
if (section.type === 'key') return `.${section.value}`
if (section.type === 'any') return '[*]'
return `[${section.value ?? section.type}]`
})
- .join('')}`
+ .join('')}${wrapperEnd}`
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 682b93ddc1d..c6964bf8458 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3016,9 +3016,12 @@ importers:
lodash:
specifier: ^4.17.21
version: 4.17.21
- tsup:
- specifier: ^8.0.2
- version: 8.0.2(@microsoft/api-extractor@7.49.0(@types/node@22.16.4))(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.3))(typescript@5.9.3)
+ rollup:
+ specifier: ^4.60.4
+ version: 4.60.4
+ rollup-plugin-dts:
+ specifier: ^6.4.1
+ version: 6.4.1(rollup@4.60.4)(typescript@5.9.3)
packages/cognitive:
dependencies:
@@ -3139,7 +3142,7 @@ importers:
specifier: ^2.0.0
version: 2.0.0
'@bpinternal/zui':
- specifier: ^2.1.1
+ specifier: ^2.2.1
version: link:../zui
'@jitl/quickjs-singlefile-browser-release-sync':
specifier: ^0.31.0
@@ -3239,7 +3242,7 @@ importers:
specifier: 1.46.0
version: link:../client
'@bpinternal/zui':
- specifier: ^2.1.1
+ specifier: ^2.2.1
version: link:../zui
browser-or-node:
specifier: ^2.1.1
@@ -3279,7 +3282,7 @@ importers:
specifier: ^1.0.1
version: 1.0.2
'@bpinternal/zui':
- specifier: ^2.1.1
+ specifier: ^2.2.1
version: link:../zui
json5:
specifier: ^2.2.3
@@ -3325,7 +3328,7 @@ importers:
specifier: ^1.0.0
version: 1.0.2
'@bpinternal/zui':
- specifier: ^2.1.1
+ specifier: ^2.2.1
version: link:../zui
json5:
specifier: ^2.2.3
@@ -3994,10 +3997,6 @@ packages:
resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
engines: {node: '>=6.9.0'}
- '@babel/code-frame@7.27.1':
- resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
- engines: {node: '>=6.9.0'}
-
'@babel/code-frame@7.29.0':
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
@@ -5256,6 +5255,9 @@ packages:
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
engines: {node: '>=6.0.0'}
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
@@ -5264,21 +5266,12 @@ packages:
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
engines: {node: '>=6.0.0'}
- '@jridgewell/sourcemap-codec@1.5.0':
- resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
-
- '@jridgewell/sourcemap-codec@1.5.4':
- resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==}
-
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
- '@jridgewell/trace-mapping@0.3.29':
- resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
-
'@jridgewell/trace-mapping@0.3.30':
resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
@@ -6006,91 +5999,216 @@ packages:
cpu: [arm]
os: [android]
+ '@rollup/rollup-android-arm-eabi@4.60.4':
+ resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==}
+ cpu: [arm]
+ os: [android]
+
'@rollup/rollup-android-arm64@4.24.2':
resolution: {integrity: sha512-iZoYCiJz3Uek4NI0J06/ZxUgwAfNzqltK0MptPDO4OR0a88R4h0DSELMsflS6ibMCJ4PnLvq8f7O1d7WexUvIA==}
cpu: [arm64]
os: [android]
+ '@rollup/rollup-android-arm64@4.60.4':
+ resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==}
+ cpu: [arm64]
+ os: [android]
+
'@rollup/rollup-darwin-arm64@4.24.2':
resolution: {integrity: sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg==}
cpu: [arm64]
os: [darwin]
+ '@rollup/rollup-darwin-arm64@4.60.4':
+ resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==}
+ cpu: [arm64]
+ os: [darwin]
+
'@rollup/rollup-darwin-x64@4.24.2':
resolution: {integrity: sha512-1F/jrfhxJtWILusgx63WeTvGTwE4vmsT9+e/z7cZLKU8sBMddwqw3UV5ERfOV+H1FuRK3YREZ46J4Gy0aP3qDA==}
cpu: [x64]
os: [darwin]
+ '@rollup/rollup-darwin-x64@4.60.4':
+ resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==}
+ cpu: [x64]
+ os: [darwin]
+
'@rollup/rollup-freebsd-arm64@4.24.2':
resolution: {integrity: sha512-1YWOpFcGuC6iGAS4EI+o3BV2/6S0H+m9kFOIlyFtp4xIX5rjSnL3AwbTBxROX0c8yWtiWM7ZI6mEPTI7VkSpZw==}
cpu: [arm64]
os: [freebsd]
+ '@rollup/rollup-freebsd-arm64@4.60.4':
+ resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==}
+ cpu: [arm64]
+ os: [freebsd]
+
'@rollup/rollup-freebsd-x64@4.24.2':
resolution: {integrity: sha512-3qAqTewYrCdnOD9Gl9yvPoAoFAVmPJsBvleabvx4bnu1Kt6DrB2OALeRVag7BdWGWLhP1yooeMLEi6r2nYSOjg==}
cpu: [x64]
os: [freebsd]
+ '@rollup/rollup-freebsd-x64@4.60.4':
+ resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==}
+ cpu: [x64]
+ os: [freebsd]
+
'@rollup/rollup-linux-arm-gnueabihf@4.24.2':
resolution: {integrity: sha512-ArdGtPHjLqWkqQuoVQ6a5UC5ebdX8INPuJuJNWRe0RGa/YNhVvxeWmCTFQ7LdmNCSUzVZzxAvUznKaYx645Rig==}
cpu: [arm]
os: [linux]
+ '@rollup/rollup-linux-arm-gnueabihf@4.60.4':
+ resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==}
+ cpu: [arm]
+ os: [linux]
+
'@rollup/rollup-linux-arm-musleabihf@4.24.2':
resolution: {integrity: sha512-B6UHHeNnnih8xH6wRKB0mOcJGvjZTww1FV59HqJoTJ5da9LCG6R4SEBt6uPqzlawv1LoEXSS0d4fBlHNWl6iYw==}
cpu: [arm]
os: [linux]
+ '@rollup/rollup-linux-arm-musleabihf@4.60.4':
+ resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==}
+ cpu: [arm]
+ os: [linux]
+
'@rollup/rollup-linux-arm64-gnu@4.24.2':
resolution: {integrity: sha512-kr3gqzczJjSAncwOS6i7fpb4dlqcvLidqrX5hpGBIM1wtt0QEVtf4wFaAwVv8QygFU8iWUMYEoJZWuWxyua4GQ==}
cpu: [arm64]
os: [linux]
+ '@rollup/rollup-linux-arm64-gnu@4.60.4':
+ resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==}
+ cpu: [arm64]
+ os: [linux]
+
'@rollup/rollup-linux-arm64-musl@4.24.2':
resolution: {integrity: sha512-TDdHLKCWgPuq9vQcmyLrhg/bgbOvIQ8rtWQK7MRxJ9nvaxKx38NvY7/Lo6cYuEnNHqf6rMqnivOIPIQt6H2AoA==}
cpu: [arm64]
os: [linux]
+ '@rollup/rollup-linux-arm64-musl@4.60.4':
+ resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.60.4':
+ resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-musl@4.60.4':
+ resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==}
+ cpu: [loong64]
+ os: [linux]
+
'@rollup/rollup-linux-powerpc64le-gnu@4.24.2':
resolution: {integrity: sha512-xv9vS648T3X4AxFFZGWeB5Dou8ilsv4VVqJ0+loOIgDO20zIhYfDLkk5xoQiej2RiSQkld9ijF/fhLeonrz2mw==}
cpu: [ppc64]
os: [linux]
+ '@rollup/rollup-linux-ppc64-gnu@4.60.4':
+ resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-musl@4.60.4':
+ resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==}
+ cpu: [ppc64]
+ os: [linux]
+
'@rollup/rollup-linux-riscv64-gnu@4.24.2':
resolution: {integrity: sha512-tbtXwnofRoTt223WUZYiUnbxhGAOVul/3StZ947U4A5NNjnQJV5irKMm76G0LGItWs6y+SCjUn/Q0WaMLkEskg==}
cpu: [riscv64]
os: [linux]
+ '@rollup/rollup-linux-riscv64-gnu@4.60.4':
+ resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.60.4':
+ resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==}
+ cpu: [riscv64]
+ os: [linux]
+
'@rollup/rollup-linux-s390x-gnu@4.24.2':
resolution: {integrity: sha512-gc97UebApwdsSNT3q79glOSPdfwgwj5ELuiyuiMY3pEWMxeVqLGKfpDFoum4ujivzxn6veUPzkGuSYoh5deQ2Q==}
cpu: [s390x]
os: [linux]
+ '@rollup/rollup-linux-s390x-gnu@4.60.4':
+ resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==}
+ cpu: [s390x]
+ os: [linux]
+
'@rollup/rollup-linux-x64-gnu@4.24.2':
resolution: {integrity: sha512-jOG/0nXb3z+EM6SioY8RofqqmZ+9NKYvJ6QQaa9Mvd3RQxlH68/jcB/lpyVt4lCiqr04IyaC34NzhUqcXbB5FQ==}
cpu: [x64]
os: [linux]
+ '@rollup/rollup-linux-x64-gnu@4.60.4':
+ resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==}
+ cpu: [x64]
+ os: [linux]
+
'@rollup/rollup-linux-x64-musl@4.24.2':
resolution: {integrity: sha512-XAo7cJec80NWx9LlZFEJQxqKOMz/lX3geWs2iNT5CHIERLFfd90f3RYLLjiCBm1IMaQ4VOX/lTC9lWfzzQm14Q==}
cpu: [x64]
os: [linux]
+ '@rollup/rollup-linux-x64-musl@4.60.4':
+ resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openbsd-x64@4.60.4':
+ resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/rollup-openharmony-arm64@4.60.4':
+ resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==}
+ cpu: [arm64]
+ os: [openharmony]
+
'@rollup/rollup-win32-arm64-msvc@4.24.2':
resolution: {integrity: sha512-A+JAs4+EhsTjnPQvo9XY/DC0ztaws3vfqzrMNMKlwQXuniBKOIIvAAI8M0fBYiTCxQnElYu7mLk7JrhlQ+HeOw==}
cpu: [arm64]
os: [win32]
+ '@rollup/rollup-win32-arm64-msvc@4.60.4':
+ resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==}
+ cpu: [arm64]
+ os: [win32]
+
'@rollup/rollup-win32-ia32-msvc@4.24.2':
resolution: {integrity: sha512-ZhcrakbqA1SCiJRMKSU64AZcYzlZ/9M5LaYil9QWxx9vLnkQ9Vnkve17Qn4SjlipqIIBFKjBES6Zxhnvh0EAEw==}
cpu: [ia32]
os: [win32]
+ '@rollup/rollup-win32-ia32-msvc@4.60.4':
+ resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.60.4':
+ resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==}
+ cpu: [x64]
+ os: [win32]
+
'@rollup/rollup-win32-x64-msvc@4.24.2':
resolution: {integrity: sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA==}
cpu: [x64]
os: [win32]
+ '@rollup/rollup-win32-x64-msvc@4.60.4':
+ resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==}
+ cpu: [x64]
+ os: [win32]
+
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
@@ -10149,6 +10267,9 @@ packages:
magic-string@0.30.12:
resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==}
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
make-dir@1.3.0:
resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==}
engines: {node: '>=4'}
@@ -11444,11 +11565,23 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
+ rollup-plugin-dts@6.4.1:
+ resolution: {integrity: sha512-l//F3Zf7ID5GoOfLfD8kroBjQKEKpy1qfhtAdnpibFZMffPaylrg1CoDC2vGkPeTeyxUe4bVFCln2EFuL7IGGg==}
+ engines: {node: '>=20'}
+ peerDependencies:
+ rollup: ^3.29.4 || ^4
+ typescript: ^4.5 || ^5.0 || ^6.0
+
rollup@4.24.2:
resolution: {integrity: sha512-do/DFGq5g6rdDhdpPq5qb2ecoczeK6y+2UAjdJ5trjQJj5f1AiVdLRWRc9A9/fFukfvJRgM0UXzxBIYMovm5ww==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
+ rollup@4.60.4:
+ resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
@@ -13823,12 +13956,6 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.1.1
- '@babel/code-frame@7.27.1':
- dependencies:
- '@babel/helper-validator-identifier': 7.27.1
- js-tokens: 4.0.0
- picocolors: 1.1.1
-
'@babel/code-frame@7.29.0':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
@@ -13891,8 +14018,8 @@ snapshots:
dependencies:
'@babel/parser': 7.27.2
'@babel/types': 7.27.1
- '@jridgewell/gen-mapping': 0.3.8
- '@jridgewell/trace-mapping': 0.3.30
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
'@babel/generator@7.29.1':
@@ -13979,7 +14106,7 @@ snapshots:
dependencies:
'@babel/core': 7.26.9
'@babel/helper-module-imports': 7.27.1
- '@babel/helper-validator-identifier': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
'@babel/traverse': 7.27.1
transitivePeerDependencies:
- supports-color
@@ -14170,13 +14297,13 @@ snapshots:
'@babel/template@7.26.9':
dependencies:
- '@babel/code-frame': 7.27.1
+ '@babel/code-frame': 7.29.0
'@babel/parser': 7.26.9
'@babel/types': 7.26.9
'@babel/template@7.27.2':
dependencies:
- '@babel/code-frame': 7.27.1
+ '@babel/code-frame': 7.29.0
'@babel/parser': 7.27.2
'@babel/types': 7.27.1
@@ -14200,7 +14327,7 @@ snapshots:
'@babel/traverse@7.27.1':
dependencies:
- '@babel/code-frame': 7.27.1
+ '@babel/code-frame': 7.29.0
'@babel/generator': 7.27.1
'@babel/parser': 7.27.2
'@babel/template': 7.27.2
@@ -14230,7 +14357,7 @@ snapshots:
'@babel/types@7.27.1':
dependencies:
'@babel/helper-string-parser': 7.27.1
- '@babel/helper-validator-identifier': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
'@babel/types@7.29.0':
dependencies:
@@ -15024,7 +15151,7 @@ snapshots:
'@jest/test-result': 29.5.0
'@jest/transform': 29.5.0
'@jest/types': 29.5.0
- '@jridgewell/trace-mapping': 0.3.29
+ '@jridgewell/trace-mapping': 0.3.31
'@types/node': 22.16.4
chalk: 4.1.2
collect-v8-coverage: 1.0.1
@@ -15052,7 +15179,7 @@ snapshots:
'@jest/source-map@29.4.3':
dependencies:
- '@jridgewell/trace-mapping': 0.3.30
+ '@jridgewell/trace-mapping': 0.3.31
callsites: 3.1.0
graceful-fs: 4.2.11
@@ -15074,7 +15201,7 @@ snapshots:
dependencies:
'@babel/core': 7.26.9
'@jest/types': 29.5.0
- '@jridgewell/trace-mapping': 0.3.29
+ '@jridgewell/trace-mapping': 0.3.31
babel-plugin-istanbul: 6.1.1
chalk: 4.1.2
convert-source-map: 2.0.0
@@ -15113,28 +15240,24 @@ snapshots:
'@jridgewell/gen-mapping@0.3.8':
dependencies:
'@jridgewell/set-array': 1.2.1
- '@jridgewell/sourcemap-codec': 1.5.0
+ '@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/trace-mapping': 0.3.25
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/set-array@1.2.1': {}
- '@jridgewell/sourcemap-codec@1.5.0': {}
-
- '@jridgewell/sourcemap-codec@1.5.4': {}
-
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.25':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
- '@jridgewell/sourcemap-codec': 1.5.0
-
- '@jridgewell/trace-mapping@0.3.29':
- dependencies:
- '@jridgewell/resolve-uri': 3.1.2
- '@jridgewell/sourcemap-codec': 1.5.4
+ '@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/trace-mapping@0.3.30':
dependencies:
@@ -15844,7 +15967,7 @@ snapshots:
'@readme/better-ajv-errors@1.6.0(ajv@8.17.1)':
dependencies:
- '@babel/code-frame': 7.27.1
+ '@babel/code-frame': 7.29.0
'@babel/runtime': 7.24.7
'@humanwhocodes/momoa': 2.0.4
ajv: 8.17.1
@@ -15903,57 +16026,132 @@ snapshots:
'@rollup/rollup-android-arm-eabi@4.24.2':
optional: true
+ '@rollup/rollup-android-arm-eabi@4.60.4':
+ optional: true
+
'@rollup/rollup-android-arm64@4.24.2':
optional: true
+ '@rollup/rollup-android-arm64@4.60.4':
+ optional: true
+
'@rollup/rollup-darwin-arm64@4.24.2':
optional: true
+ '@rollup/rollup-darwin-arm64@4.60.4':
+ optional: true
+
'@rollup/rollup-darwin-x64@4.24.2':
optional: true
+ '@rollup/rollup-darwin-x64@4.60.4':
+ optional: true
+
'@rollup/rollup-freebsd-arm64@4.24.2':
optional: true
+ '@rollup/rollup-freebsd-arm64@4.60.4':
+ optional: true
+
'@rollup/rollup-freebsd-x64@4.24.2':
optional: true
+ '@rollup/rollup-freebsd-x64@4.60.4':
+ optional: true
+
'@rollup/rollup-linux-arm-gnueabihf@4.24.2':
optional: true
+ '@rollup/rollup-linux-arm-gnueabihf@4.60.4':
+ optional: true
+
'@rollup/rollup-linux-arm-musleabihf@4.24.2':
optional: true
+ '@rollup/rollup-linux-arm-musleabihf@4.60.4':
+ optional: true
+
'@rollup/rollup-linux-arm64-gnu@4.24.2':
optional: true
+ '@rollup/rollup-linux-arm64-gnu@4.60.4':
+ optional: true
+
'@rollup/rollup-linux-arm64-musl@4.24.2':
optional: true
+ '@rollup/rollup-linux-arm64-musl@4.60.4':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.60.4':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.60.4':
+ optional: true
+
'@rollup/rollup-linux-powerpc64le-gnu@4.24.2':
optional: true
+ '@rollup/rollup-linux-ppc64-gnu@4.60.4':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.60.4':
+ optional: true
+
'@rollup/rollup-linux-riscv64-gnu@4.24.2':
optional: true
+ '@rollup/rollup-linux-riscv64-gnu@4.60.4':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.60.4':
+ optional: true
+
'@rollup/rollup-linux-s390x-gnu@4.24.2':
optional: true
+ '@rollup/rollup-linux-s390x-gnu@4.60.4':
+ optional: true
+
'@rollup/rollup-linux-x64-gnu@4.24.2':
optional: true
+ '@rollup/rollup-linux-x64-gnu@4.60.4':
+ optional: true
+
'@rollup/rollup-linux-x64-musl@4.24.2':
optional: true
+ '@rollup/rollup-linux-x64-musl@4.60.4':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.60.4':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.60.4':
+ optional: true
+
'@rollup/rollup-win32-arm64-msvc@4.24.2':
optional: true
+ '@rollup/rollup-win32-arm64-msvc@4.60.4':
+ optional: true
+
'@rollup/rollup-win32-ia32-msvc@4.24.2':
optional: true
+ '@rollup/rollup-win32-ia32-msvc@4.60.4':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.60.4':
+ optional: true
+
'@rollup/rollup-win32-x64-msvc@4.24.2':
optional: true
+ '@rollup/rollup-win32-x64-msvc@4.60.4':
+ optional: true
+
'@rtsao/scc@1.1.0': {}
'@rushstack/node-core-library@5.10.1(@types/node@22.16.4)':
@@ -19214,7 +19412,7 @@ snapshots:
estree-walker@3.0.3:
dependencies:
- '@types/estree': 1.0.6
+ '@types/estree': 1.0.8
esutils@2.0.3: {}
@@ -20666,7 +20864,7 @@ snapshots:
jest-message-util@29.5.0:
dependencies:
- '@babel/code-frame': 7.27.1
+ '@babel/code-frame': 7.29.0
'@jest/types': 29.5.0
'@types/stack-utils': 2.0.1
chalk: 4.1.2
@@ -21291,7 +21489,11 @@ snapshots:
magic-string@0.30.12:
dependencies:
- '@jridgewell/sourcemap-codec': 1.5.0
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
make-dir@1.3.0:
dependencies:
@@ -22304,7 +22506,7 @@ snapshots:
parse-json@5.2.0:
dependencies:
- '@babel/code-frame': 7.27.1
+ '@babel/code-frame': 7.29.0
error-ex: 1.3.2
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
@@ -22917,6 +23119,17 @@ snapshots:
dependencies:
glob: 7.2.3
+ rollup-plugin-dts@6.4.1(rollup@4.60.4)(typescript@5.9.3):
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ '@jridgewell/sourcemap-codec': 1.5.5
+ convert-source-map: 2.0.0
+ magic-string: 0.30.21
+ rollup: 4.60.4
+ typescript: 5.9.3
+ optionalDependencies:
+ '@babel/code-frame': 7.29.0
+
rollup@4.24.2:
dependencies:
'@types/estree': 1.0.6
@@ -22941,6 +23154,37 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.24.2
fsevents: 2.3.3
+ rollup@4.60.4:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.60.4
+ '@rollup/rollup-android-arm64': 4.60.4
+ '@rollup/rollup-darwin-arm64': 4.60.4
+ '@rollup/rollup-darwin-x64': 4.60.4
+ '@rollup/rollup-freebsd-arm64': 4.60.4
+ '@rollup/rollup-freebsd-x64': 4.60.4
+ '@rollup/rollup-linux-arm-gnueabihf': 4.60.4
+ '@rollup/rollup-linux-arm-musleabihf': 4.60.4
+ '@rollup/rollup-linux-arm64-gnu': 4.60.4
+ '@rollup/rollup-linux-arm64-musl': 4.60.4
+ '@rollup/rollup-linux-loong64-gnu': 4.60.4
+ '@rollup/rollup-linux-loong64-musl': 4.60.4
+ '@rollup/rollup-linux-ppc64-gnu': 4.60.4
+ '@rollup/rollup-linux-ppc64-musl': 4.60.4
+ '@rollup/rollup-linux-riscv64-gnu': 4.60.4
+ '@rollup/rollup-linux-riscv64-musl': 4.60.4
+ '@rollup/rollup-linux-s390x-gnu': 4.60.4
+ '@rollup/rollup-linux-x64-gnu': 4.60.4
+ '@rollup/rollup-linux-x64-musl': 4.60.4
+ '@rollup/rollup-openbsd-x64': 4.60.4
+ '@rollup/rollup-openharmony-arm64': 4.60.4
+ '@rollup/rollup-win32-arm64-msvc': 4.60.4
+ '@rollup/rollup-win32-ia32-msvc': 4.60.4
+ '@rollup/rollup-win32-x64-gnu': 4.60.4
+ '@rollup/rollup-win32-x64-msvc': 4.60.4
+ fsevents: 2.3.3
+
router@2.2.0:
dependencies:
debug: 4.4.3
@@ -23757,7 +24001,7 @@ snapshots:
joycon: 3.1.1
postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.3))
resolve-from: 5.0.0
- rollup: 4.24.2
+ rollup: 4.60.4
source-map: 0.8.0-beta.0
sucrase: 3.35.0
tree-kill: 1.2.2
@@ -24070,7 +24314,7 @@ snapshots:
v8-to-istanbul@9.1.0:
dependencies:
- '@jridgewell/trace-mapping': 0.3.30
+ '@jridgewell/trace-mapping': 0.3.31
'@types/istanbul-lib-coverage': 2.0.4
convert-source-map: 1.9.0