Repository: cubicforms/chemical-x-forms
Pull request #40: Add full type safety for v-xmodel directive
Author: login: ozzyfromspace
Created: 2025-03-16T22:07:08Z | Merged: 2025-03-16T22:09:18Z

Also achieves #31 (exporting types via `@chemical-x/forms/types`)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Improved type definitions for form directives, offering enhanced type safety.
  - Introduced a streamlined alias for easier imports of form-related types.
  - Declared compatibility with the latest Nuxt version.

- **Refactor**
  - Renamed options and updated configurations for clearer API usage.
  - Refined event handling in directives to bolster robustness.

- **Chores**
  - Removed an outdated type-checking script to streamline the build process.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/package.json b/package.json
index 9f1feb7..80020e0 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,6 @@
     "lint:fix": "eslint . --fix",
     "prepack": "nuxt-module-build build",
     "release": "pnpm lint && pnpm test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
-    "typecheck": "tsc --noEmit",
     "test": "vitest run",
     "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
     "test:watch": "vitest watch"
@@ -49,6 +48,13 @@
       "types": "./dist/runtime/adapters/*/index.d.ts",
       "import": "./dist/runtime/adapters/*/index.mjs",
       "require": "./dist/runtime/adapters/*/index.cjs"
+    },
+    "./types": {
+      "types": "./dist/runtime/types/types-api.d.ts",
+      "import": null,
+      "require": null,
+      "default": "./dist/runtime/types/types-api.d.ts",
+      "node": "./dist/runtime/types/types-api.d.ts"
     }
   },
   "keywords": [
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index 690a6e7..79b94df 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -1,5 +1,8 @@
 export default defineNuxtConfig({
   modules: ["../src/module"],
   devtools: { enabled: true },
+  alias: {
+    "@chemical-x/forms/types": "../src/runtime/types/type-api.ts"
+  },
   compatibilityDate: "2025-01-28",
 })
diff --git a/playground/tsconfig.json b/playground/tsconfig.json
index 4b34df1..eea9fcb 100644
--- a/playground/tsconfig.json
+++ b/playground/tsconfig.json
@@ -1,3 +1,10 @@
 {
-  "extends": "./.nuxt/tsconfig.json"
-}
+  "extends": "./.nuxt/tsconfig.json",
+  "compilerOptions": {
+    "paths": {
+      "@chemical-x/forms/types": [
+        "../src/runtime/types/types-api.ts"
+      ]
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/module.ts b/src/module.ts
index af2c0a5..a12b930 100644
--- a/src/module.ts
+++ b/src/module.ts
@@ -10,17 +10,20 @@ import { selectNodeTransform } from "./runtime/lib/core/transforms/select-transf
 
 // Module options TypeScript interface definition
 // eslint-disable-next-line @typescript-eslint/no-empty-object-type
-export interface ModuleOptions {}
+export interface CXModuleOptions {}
 
-export default defineNuxtModule<ModuleOptions>({
+export default defineNuxtModule<CXModuleOptions>({
   meta: {
     name: "chemical-x-forms",
     configKey: "chemicalX",
+    compatibility: {
+      nuxt: "^3.0.0"
+    }
   },
   defaults: {},
-  setup(_options, _nuxt) {
-    _nuxt.options.vue.compilerOptions.nodeTransforms ||= []
-    _nuxt.options.vue.compilerOptions.nodeTransforms.push(
+  setup(_options, nuxt) {
+    nuxt.options.vue.compilerOptions.nodeTransforms ||= []
+    nuxt.options.vue.compilerOptions.nodeTransforms.push(
       selectNodeTransform,
       inputTextAreaNodeTransform
     )
@@ -42,11 +45,12 @@ export default defineNuxtModule<ModuleOptions>({
     addTypeTemplate({
       filename: "types/v-xmodel.d.ts",
       getContents: () => `// Generated by @chemical-x/forms
-import type { VXmodelDirective } from "./runtime/plugins/xmodel.ts"
+import type { ObjectDirective } from "vue"
+import type { RegisterDirective } from "@chemical-x/forms/types"
 
 declare module "vue" {
   interface GlobalDirectives { 
-    vXmodel: ObjectDirective<HTMLElement, VXmodelDirective>
+    vXmodel: RegisterDirective
   }
 }
 
diff --git a/src/runtime/plugins/xmodel.ts b/src/runtime/plugins/xmodel.ts
index e233f06..47fdc5c 100644
--- a/src/runtime/plugins/xmodel.ts
+++ b/src/runtime/plugins/xmodel.ts
@@ -20,10 +20,9 @@ import {
   nextTick,
   warn,
 } from "vue"
-import type { XModelValue } from "../types/types-api"
-
-type AssignerFn = (value: unknown) => void
+import type { CustomDirectiveRegisterAssignerFn, RegisterCheckboxCustomDirective, RegisterModelDynamicCustomDirective, RegisterRadioCustomDirective, RegisterSelectCustomDirective, RegisterTextCustomDirective, XModelValue } from "../types/types-api"
 
+export const assignKey: unique symbol = Symbol("_assign")
 function isXModelPayload<Value = unknown>(val: unknown): val is XModelValue<Value> {
   if (!val) return false
   if (typeof val !== "object") return false
@@ -46,7 +45,7 @@ function addEventListener(
   el.addEventListener(event, handler, options)
 }
 
-const getModelAssigner = (vnode: VNode, xmodelValue: XModelValue): AssignerFn => {
+const getModelAssigner = (vnode: VNode, xmodelValue: XModelValue): CustomDirectiveRegisterAssignerFn => {
   const fn
       = vnode.props?.["onUpdate:xmodelValue"] // this is a developer escape hatch
   if (!fn) {
@@ -73,9 +72,7 @@ function onCompositionEnd(e: Event) {
   }
 }
 
-const assignKey: unique symbol = Symbol("_assign")
-
-function setAssignFunction(el: { [assignKey]: AssignerFn }, vnode: VNode, value: unknown) {
+function setAssignFunction(el: { [AssignKey: symbol]: CustomDirectiveRegisterAssignerFn }, vnode: VNode, value: unknown) {
   if (!isXModelPayload(value)) {
     warn(`vmodel expected value of type XModelValue, got value of type ${typeof value} instead. Please check your v-xmodel value.`)
     el[assignKey] = _ => undefined
@@ -87,17 +84,9 @@ function setAssignFunction(el: { [assignKey]: AssignerFn }, vnode: VNode, value:
   }
 }
 
-type CustomDirective<T, Modifiers extends string = string> = ObjectDirective<T & {
-  _assigning?: boolean
-  [assignKey]: AssignerFn
-}, XModelValue, Modifiers, string>
-
 // We are exporting the v-model runtime directly as vnode hooks so that it can
 // be tree-shaken in case v-model is never used.
-const vXModelText: CustomDirective<
-    HTMLInputElement | HTMLTextAreaElement,
-    "trim" | "number" | "lazy"
-> = {
+const vXModelText: RegisterTextCustomDirective = {
   created(el, { value, modifiers: { lazy, trim, number } }, vnode) {
     const castToNumber
         = number || (vnode.props && vnode.props["type"] === "number")
@@ -115,7 +104,7 @@ const vXModelText: CustomDirective<
       if (castToNumber) {
         domValue = looseToNumber(domValue)
       }
-      el[assignKey](domValue)
+      el[assignKey]?.(domValue)
     })
     if (trim) {
       addEventListener(el, "change", () => {
@@ -173,7 +162,7 @@ const vXModelText: CustomDirective<
   },
 }
 
-const vXModelCheckbox: CustomDirective<HTMLInputElement> = {
+const vXModelCheckbox: RegisterCheckboxCustomDirective = {
   // #4096 array checkboxes need to be deep traversed
   deep: true,
   created(el, { value }, vnode) {
@@ -198,12 +187,12 @@ const vXModelCheckbox: CustomDirective<HTMLInputElement> = {
         const index = looseIndexOf(modelValue, elementValue)
         const found = index !== -1
         if (checked && !found) {
-          assign(modelValue.concat(elementValue))
+          assign?.(modelValue.concat(elementValue))
         }
         else if (!checked && found) {
           const filtered = [...modelValue]
           filtered.splice(index, 1)
-          assign(filtered)
+          assign?.(filtered)
         }
       }
       else if (isSet(modelValue)) {
@@ -218,10 +207,10 @@ const vXModelCheckbox: CustomDirective<HTMLInputElement> = {
         else {
           cloned.delete(elementValue)
         }
-        assign(cloned)
+        assign?.(cloned)
       }
       else {
-        assign(getCheckboxValue(el, checked))
+        assign?.(getCheckboxValue(el, checked))
       }
     })
   },
@@ -266,7 +255,7 @@ function setChecked(
   }
 }
 
-const vXModelRadio: CustomDirective<HTMLInputElement> = {
+const vXModelRadio: RegisterRadioCustomDirective = {
   created(el, { value }, vnode) {
     if (!isXModelPayload(value)) return
 
@@ -275,7 +264,7 @@ const vXModelRadio: CustomDirective<HTMLInputElement> = {
     el.checked = looseEqual(value.innerRef.value, vnode.props?.["value"])
     setAssignFunction(el, vnode, value)
     addEventListener(el, "change", () => {
-      el[assignKey](getValue(el))
+      el[assignKey]?.(getValue(el))
     })
   },
   beforeUpdate(el, { value, oldValue }, vnode) {
@@ -288,7 +277,7 @@ const vXModelRadio: CustomDirective<HTMLInputElement> = {
   },
 }
 
-const vXModelSelect: CustomDirective<HTMLSelectElement, "number"> = {
+const vXModelSelect: RegisterSelectCustomDirective = {
   // <select multiple> value need to be deep traversed
   deep: true,
   created(el, { value, modifiers: { number } }, vnode) {
@@ -303,7 +292,7 @@ const vXModelSelect: CustomDirective<HTMLSelectElement, "number"> = {
         .map((o: HTMLOptionElement) =>
           number ? looseToNumber(getValue(o)) : getValue(o),
         )
-      el[assignKey](
+      el[assignKey]?.(
         el.multiple
           ? isSetModel
             ? new Set(selectedVal)
@@ -391,9 +380,7 @@ function getCheckboxValue(
   return key in el ? el[key] : checked
 }
 
-const vXModelDynamic: ObjectDirective<
-    HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, XModelValue, "trim" | "number" | "lazy"
-> = {
+const vXModelDynamic: RegisterModelDynamicCustomDirective = {
   created(el, binding, vnode) {
     callModelHook(el, binding, vnode, null, "created")
   },
diff --git a/src/runtime/types/types-api.ts b/src/runtime/types/types-api.ts
index e2e16cc..cb273b0 100644
--- a/src/runtime/types/types-api.ts
+++ b/src/runtime/types/types-api.ts
@@ -1,4 +1,4 @@
-import type { Ref } from "vue"
+import type { ObjectDirective, Ref } from "vue"
 import type { DeepPartial, FlatPath, GenericForm, NestedType } from "./types-core"
 
 export type FormKey = string
@@ -128,6 +128,30 @@ export type XModelValue<Value = unknown> = {
   setValueWithInternalPath: (value: unknown) => boolean
 }
 
+export type CustomDirectiveRegisterAssignerFn = (value: unknown) => void
+export type CustomRegisterDirective<T, Modifiers extends string = string> = ObjectDirective<T & {
+  _assigning?: boolean
+  [S: symbol]: CustomDirectiveRegisterAssignerFn
+}, XModelValue, Modifiers, string>
+
+export type RegisterTextCustomDirective = CustomRegisterDirective<
+HTMLInputElement | HTMLTextAreaElement,
+"trim" | "number" | "lazy"
+>
+
+export type RegisterCheckboxCustomDirective = CustomRegisterDirective<HTMLInputElement>
+export type RegisterRadioCustomDirective = CustomRegisterDirective<HTMLInputElement>
+export type RegisterSelectCustomDirective = CustomRegisterDirective<HTMLSelectElement, "number">
+export type RegisterModelDynamicCustomDirective = ObjectDirective<
+HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, XModelValue, "trim" | "number" | "lazy"
+>
+export type RegisterDirective =
+  | RegisterTextCustomDirective
+  | RegisterCheckboxCustomDirective
+  | RegisterSelectCustomDirective
+  | RegisterRadioCustomDirective
+  | RegisterModelDynamicCustomDirective
+
 // undefined by default (defer to useForm global setting)
 export type RegisterContext<Input, Output> = {
   fieldTransformer?: undefined | boolean | FieldTransformer<Input, Output>


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>src/runtime/plugins/xmodel.ts (1)</summary><blockquote>

`75-85`: _⚠️ Potential issue_

**Possible property signature mismatch in function parameter.**

```ts
function setAssignFunction(el: { [AssignKey: symbol]: CustomDirectiveRegisterAssignerFn }, vnode: VNode, value: unknown) {
  ...
  el[assignKey] = ...
}
```
Here, the parameter uses `[AssignKey: symbol]`, but the code sets `el[assignKey]`. This mismatch likely won’t compile as intended.  

Consider updating your function signature to:
```diff
-function setAssignFunction(el: { [AssignKey: symbol]: CustomDirectiveRegisterAssignerFn }, vnode: VNode, value: unknown) {
+function setAssignFunction(el: { [assignKey]: CustomDirectiveRegisterAssignerFn }, vnode: VNode, value: unknown) {
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>src/module.ts (1)</summary><blockquote>

`24-25`: **Underscored parameter usage.**

Renaming `_options` to `_unused` or removing it altogether might be clearer if there's no plan to use it. Otherwise, this pattern is acceptable for ignoring parameters.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9c42c17d2d4bf21a541b68961b020082fea0cacd and 0d0b82e6cbcc8b879f015277b9c97f68bd846f83.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `package.json` (1 hunks)
* `playground/nuxt.config.ts` (1 hunks)
* `playground/tsconfig.json` (1 hunks)
* `src/module.ts` (2 hunks)
* `src/runtime/plugins/xmodel.ts` (13 hunks)
* `src/runtime/types/types-api.ts` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>src/module.ts</summary>

[error] 13-13: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.


(lint/suspicious/noEmptyInterface)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (22)</summary><blockquote>

<details>
<summary>playground/tsconfig.json (1)</summary>

`3-9`: **TypeScript path alias for types looks good**

Adding the path alias in the TypeScript configuration properly maps `@chemical-x/forms/types` to the types-api.ts file, which aligns with the PR goal of exporting types through a dedicated module.

</details>
<details>
<summary>package.json (1)</summary>

`52-57`: **Proper type export configuration**

The new export entry for `./types` correctly exposes the type definitions through the package exports field. Setting both `import` and `require` to null while providing the type definitions path for `types`, `default`, and `node` is the right approach for publishing TypeScript types that should be accessible through import paths.

</details>
<details>
<summary>src/runtime/types/types-api.ts (4)</summary>

`131-135`: **Well-structured custom directive type definition**

The `CustomDirectiveRegisterAssignerFn` and `CustomRegisterDirective` base types provide a solid foundation for type-safe directives. The inclusion of the `_assigning` boolean and symbol-indexed property is a good approach for handling the assignment function.

---

`137-140`: **Type-safe text input directive**

The `RegisterTextCustomDirective` correctly constrains the element types and modifiers, providing better type checking for text inputs and textareas.

---

`142-147`: **Specialized element-specific directive types**

Creating separate directive types for different form elements (checkbox, radio, select) with their specific modifiers enhances type safety and aligns with the PR goal.

---

`148-153`: **Comprehensive union type for all directive variants**

The `RegisterDirective` union type properly combines all the specific directive types, making it easy to use a single type when the specific element type isn't known in advance. This improves the developer experience when working with the directive system.

</details>
<details>
<summary>src/module.ts (4)</summary>

`15-15`: **Module definition looks good.**

Defining the module with `CXModuleOptions` is aligned with the rest of your codebase. Future configuration can be added as needed.

---

`48-49`: **Imports for type definitions seem correct.**

These newly added imports from "vue" and "@chemical-x/forms/types" match the changes in your directive definitions.

---

`53-53`: **Updated directive type reference.**

Using `RegisterDirective` for `vXmodel` ensures consistent, specialized typing for your directive in the global Vue scope.

---

`19-21`: <details>
<summary>❓ Verification inconclusive</summary>

**Confirm Nuxt compatibility range.**

You pinned the compatibility to `^3.0.0`. Ensure this aligns with your target versions or if future versions may need explicit coverage.  

Would you like to verify compatibility with newer minor releases?

---

🏁 Script executed:

```shell
#!/bin/bash
# Searching for potential known issues with chemical-x-forms in newer Nuxt versions:
rg -A 3 "nuxt" 
```

Length of output: 23361

---



</details>

**Verify Nuxt Compatibility Range in src/module.ts**

In the compatibility configuration you’ve declared Nuxt support as `"^3.0.0"`, which—according to semver—covers all Nuxt 3 releases. However, several other parts of the repository (e.g., in package.json and pnpm-lock.yaml) show that we’re testing and using versions around 3.15–3.16. This discrepancy may lead to confusion about the minimum supported version. Please confirm whether the module is intended to support all Nuxt 3 versions or if the lower bound should be raised (for example, to `"^3.16.0"`) to reflect the currently tested range. Adjust the compatibility field or document the intended range accordingly.

</details>
<details>
<summary>src/runtime/plugins/xmodel.ts (12)</summary>

`23-23`: **New type imports are aligned with the updated directive strategy.**

Bringing in directive-related types from `../types/types-api` is consistent with your restructured approach to v-xmodel.

---

`25-25`: **Symbol for assignment key.**

Declaring a unique symbol named `assignKey` is a concise approach. Just ensure consistency when referencing this symbol in object property signatures throughout the file.

---

`48-58`: **Updated return type for enhanced type safety.**

Changing the return type from a generic assigner to `CustomDirectiveRegisterAssignerFn` helps ensure data integrity in `getModelAssigner`.

---

`89-89`: **Directive typed as RegisterTextCustomDirective.**

Replacing a generic directive with `RegisterTextCustomDirective` clarifies the directive’s contract.

---

`107-107`: **Optional chaining prevents runtime errors.**

`el[assignKey]?.(domValue)` safely calls the assign function only if it’s defined.

---

`165-165`: **CheckBox directive signature updated for stronger typing.**

`RegisterCheckboxCustomDirective` eliminates guesswork about possible inputs.

---

`190-213`: **Optional chaining logic is correct.**

Using `assign?.()` ensures no exceptions if the assign function is undefined.

---

`258-258`: **Radio directive signature updated.**

`RegisterRadioCustomDirective` further refines the directive’s interface.

---

`267-267`: **Ensure safe calls with optional chaining.**

`el[assignKey]?.(getValue(el))` is a neat way to handle an unset assign function.

---

`280-280`: **Select directive signature updated.**

`RegisterSelectCustomDirective` ensures typed behavior for select elements.

---

`295-295`: **Maintain null safety with optional chaining.**

Again, this usage avoids runtime errors if the assign function isn't present.

---

`383-383`: **Dynamic directive signature updated.**

`RegisterModelDynamicCustomDirective` standardizes the approach for dynamic v-xmodel directives.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-16T22:11:27Z

Review comments:
diff_hunk: @@ -1,5 +1,8 @@
 export default defineNuxtConfig({
   modules: ["../src/module"],
   devtools: { enabled: true },
+  alias: {
+    "@chemical-x/forms/types": "../src/runtime/types/type-api.ts"
+  },
path: playground/nuxt.config.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix path inconsistency between TypeScript and Nuxt configurations**

There's a path inconsistency between your Nuxt and TypeScript configurations:
- tsconfig.json points to: `../src/runtime/types/types-api.ts`
- nuxt.config.ts points to: `../src/runtime/types/type-api.ts`

The missing "s" in "type-api.ts" will cause module resolution failures.


```diff
 alias: {
-  "@chemical-x/forms/types": "../src/runtime/types/type-api.ts"
+  "@chemical-x/forms/types": "../src/runtime/types/types-api.ts"
 },
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  alias: {
    "@chemical-x/forms/types": "../src/runtime/types/types-api.ts"
  },
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-16T22:11:26Z
author_association: NONE
diff_hunk: @@ -10,17 +10,20 @@ import { selectNodeTransform } from "./runtime/lib/core/transforms/select-transf
 
 // Module options TypeScript interface definition
 // eslint-disable-next-line @typescript-eslint/no-empty-object-type
-export interface ModuleOptions {}
+export interface CXModuleOptions {}
path: src/module.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Consider replacing the empty interface with a type alias.**

Static analysis warns that an empty interface is essentially the same as `{}`. Use a type alias or add the intended fields to this interface.  

```diff
-export interface CXModuleOptions {}
+export type CXModuleOptions = Record<string, unknown>
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export type CXModuleOptions = Record<string, unknown>
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 13-13: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.


(lint/suspicious/noEmptyInterface)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-16T22:11:26Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-16T22:07:15Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This update modifies several configuration and source files to adjust type handling and module resolution. In _package.json_, a type checking script is removed and new export entries for type definitions are added. The playground configuration files now include an alias mapping for a forms types module. In the source, an interface is renamed and the module’s export and metadata are updated. The xmodel plugin and associated types are refactored to use new custom directive types and improved type safety, with updated function signatures and error-proofing using optional chaining.

## Changes

| File(s)                                          | Summary                                                                                                                                                                                                                                                  |
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package.json`                                   | **Removed:** `"typecheck": "tsc --noEmit"`<br>**Added:** Export entry `"./types"` pointing to `./dist/runtime/types/types-api.d.ts` with keys `"types"`, `"default"`, `"node"` and `null` for `"import"` and `"require"`.                   |
| `playground/nuxt.config.ts` <br> `playground/tsconfig.json` | **Added:** Alias mapping for `@chemical-x/forms/types`.<br>In `nuxt.config.ts`, alias points to `"../src/runtime/types/type-api.ts"`, and in `tsconfig.json`, the compiler options now map the alias to `"../src/runtime/types/types-api.ts"`.              |
| `src/module.ts`                                  | **Renamed:** Interface from `ModuleOptions` to `CXModuleOptions`.<br>**Updated:** Default export to use `CXModuleOptions`; added a Nuxt compatibility declaration; renamed the `_nuxt` parameter to `nuxt`; updated directive type imports and definitions. |
| `src/runtime/plugins/xmodel.ts`                  | **Modified:** Replaced the `AssignerFn` type with `CustomDirectiveRegisterAssignerFn` in functions `getModelAssigner` and `setAssignFunction`.<br>**Updated:** Directive variables (`vXModelText`, `vXModelCheckbox`, etc.) to use new Register* types; implemented optional chaining in assignment. |
| `src/runtime/types/types-api.ts`                 | **Added:** New exported type definitions including `CustomDirectiveRegisterAssignerFn`, `CustomRegisterDirective`, and various `Register*CustomDirective` types, along with a union type `RegisterDirective` for comprehensive type support.                         |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
  participant App as Nuxt Application
  participant Module as CXModule Module
  participant Plugin as xmodel Plugin

  App->>Module: Initialize module with CXModuleOptions
  Module->>Plugin: Register and setup xmodel directives
  Plugin->>Plugin: Use CustomDirectiveRegisterAssignerFn for value updates
  Plugin->>Module: Expose updated RegisterDirective for directive registration
```

## Poem

> I'm a rabbit on a coding spree,  
> Hopping through aliases and types with glee.  
> Modules renamed and scripts refined,  
> Directives updated, perfectly aligned.  
> With each new change, I twirl and hop—  
> In our code garden, magic will never stop!  
> 🐇✨  


<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡🧪 Multi-step agentic review comment chat (experimental)</summary>
> 
> - We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
> 						- To enable this feature, set early_access to true under in the settings.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 🔥 Error while generating docstrings. (♻️  Check again to generate again)

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=cubicforms/chemical-x-forms&utm_content=40):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-17T00:39:02Z
body: > [!CAUTION]
> An unexpected error occurred while opening a pull request: Not Found - https://docs.github.com/rest/git/refs#get-a-reference
author_association: NONE