Skip to content

Conversation

@EECOLOR
Copy link
Member

@EECOLOR EECOLOR commented Nov 6, 2025

Summary by CodeRabbit

  • New Features

    • Added typed form utilities, new render‑prop components, specialized field hooks, richer validators and type helpers with accompanying type tests.
  • Refactor

    • Consolidated and expanded the TypeScript type surface and JSDoc across forms, schemas, snapshots and subscriptions; subscription notification behavior adjusted.
  • Chores

    • Stricter TypeScript config, new type‑test tooling, a postinstall helper for peer deps, and package metadata updates.
  • Removed

    • Example Firebase app and its example files/assets removed.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 6, 2025

Walkthrough

Adds a comprehensive TypeScript type surface and many type-only tests, tightens JSDoc/type annotations across hooks/fields/snapshot/state, introduces a postinstall script to install peer deps into a new typescript/ folder, updates TS configs/package.json, adds small runtime wrappers/helpers, and removes large parts of the example-firebase example.

Changes

Cohort / File(s) Summary
Repo & TS infra
\.gitignore`, `tsconfig.json`, `tsconfig.type.test.json`, `example/tsconfig.json``
Add typescript/ to gitignore; stricter TS settings (module/node16, moduleResolution/node16, jsx/react-jsx, noImplicitAny/strictNullChecks, allowImportingTsExtensions), path mappings, and new tsconfig for type tests.
Package & install script
package.json, \scripts/install-peer-dependencies-for-typescript.js``
Add scripts (postinstall, test.types), peer/dev deps (react, @types/react, typescript, @types/node), and a postinstall helper that ensures typescript/ package.json and installs peer dependencies via yarn (sets PREVENT_LOOP).
Core types & barrel
\src/types.ts`, `types.ts``
Add a large, additive TypeScript type surface (Field, FieldSchema, NormalizedField, State, Snapshot, validation types, utilities) and a root-level types.ts re-export.
Removed legacy globals/types
\src/types.d.ts`, `types/global.d.ts`, `example/types/global.d.ts``
Remove legacy src/types.d.ts and several global declare const React (and related) declarations.
Type-test infra & helpers
\src/type.test.helpers.ts`, `scripts/tsconfig.json``
Add test-only type utilities (expectNotAny/expectNotNever/expectAssignable, Prepared, etc.) and a scripts tsconfig.
Type tests
\src/*.type.test.ts`` (components, fields, hooks, normalize, schema, snapshot, ...)
Add many *.type.test.ts files validating static typing for components, fields, hooks, normalize, schema, snapshot, and related systems.
Runtime components & small helpers
\src/components.js`, `src/type-helpers.js``
Add render-prop wrappers (FormFieldValue, FormFieldsValues, FormFieldValid) and identity helpers asConst / asAny plus small valueOrNull.
Hooks — typings & API adjustment
\src/hooks.js`, `src/hooks.type.test.ts``
Stronger JSDoc generics and typings; add useNumberFormField, useBooleanFormField, useArrayFormField; change useObjectFormField to return fields; add useFormFieldSnapshot.
Fields, normalize & utilities
\src/fields.js`, `src/normalize.js`, `src/fields.type.test.ts``
Add richer JSDoc/templates and helpers (updateState, pick, bindValidate, mapValues, addParent), tighten readonly/value semantics; normalize gets default name param and explicit generic cast.
Schema & validation API
\src/schema.js`, `src/validation.js`, `validation.js``
Add typed overloads/templates for object/array; introduce/export typed validation variants (requiredT, optionalT, withT), annotate validators, and switch some @ts-ignore@ts-expect-error.
Snapshot & state subscriptions
\src/snapshot.js`, `src/state.js`, `src/snapshot.type.test.ts``
Add JSDoc typing scaffolding and explicit Snapshot casts; export subscribeToAll and subscribeToChildren; change subscribeToAll notifier to notify(newState) (oldState removed).
Validation runtime exports
\validation.js`, `src/validation.js``
Expand exported surface to include requiredT, optionalT, withT alongside existing validators; add templates/annotations.
Example project edits & removals
\example-firebase//*`, `example//`, `example/src/**/``
Remove large parts of example-firebase (configs, services, scripts, CSS, HTML, package.json, tsconfig, globals); update example project scripts/configs, add small README line, and add JSDoc/type annotation changes in example sources.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Root as root package.json
    participant Postinstall as scripts/install-peer-dependencies-for-typescript.js
    participant FS as filesystem (typescript/)
    participant Yarn as yarn
    Root->>Postinstall: run postinstall
    Postinstall->>Postinstall: read peerDependencies
    Postinstall->>FS: ensure `typescript/` dir and package.json
    Postinstall->>Yarn: spawn `yarn add <peer@version> --pure-lockfile` (cwd=typescript/)
    Yarn-->>Postinstall: stream logs and exit code
    Postinstall->>Postinstall: exit with yarn code (PREVENT_LOOP=true)
Loading
sequenceDiagram
    autonumber
    participant Caller as subscribeToAll caller
    participant subscribeToAll as src/state.subscribeToAll
    participant Child as child subscription
    Caller->>subscribeToAll: subscribe({ state, childrenFromState, notify, ... })
    subscribeToAll->>Child: subscribeToChild(...) for each derived child
    Child-->>subscribeToAll: child notifies
    subscribeToAll->>Caller: notify(newState)  %% oldState removed
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add types to the library' clearly summarizes the main change: adding TypeScript type support throughout the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 89.11% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch typed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@socket-security
Copy link

socket-security bot commented Nov 6, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​react@​18.3.261001007995100
Updated@​types/​node@​13.13.42 ⏵ 22.19.010010081 +295100
Addedreact-fast-compare@​3.0.110010010084100
Addedtypescript@​5.9.31001009010090
Added@​kaliber/​serve@​1.0.5100100100100100
Updated@​kaliber/​build@​0.0.102 ⏵ 3.1.0100 +28100100 +30100 +13100

View full report

Copy link
Member Author

@EECOLOR EECOLOR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Je moet nog testen of het goed werkt wanneer je de library in een project gebruikt
  • Je moet nog een test.types script toevoegen die de *.type.test.ts file compiled

@EECOLOR EECOLOR marked this pull request as ready for review November 6, 2025 22:02
@EECOLOR EECOLOR requested a review from AlbertSmit November 6, 2025 22:02
@EECOLOR
Copy link
Member Author

EECOLOR commented Nov 6, 2025

@buildersteven4 I can only add you as reviewer once you have accepted the invitation :-D

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/state.js (1)

52-61: Preserve the (newState, oldState) notification contract.

notify used to receive both newState and oldState, and callers still rely on that signature. Dropping the second argument leaves listeners with oldState === undefined, silently breaking diff-based consumers. Please keep passing both values through.

-    if (onlyNotifyOnChildChange && !childrenChanged) return
-    notify(newState)
+    if (onlyNotifyOnChildChange && !childrenChanged) return
+    notify(newState, oldState)
src/fields.js (3)

55-57: Reset must clear object-level errors.

Line 55 currently replays the old error because updateState spreads the previous state before applying the pick. After a validation failure, calling reset() leaves state.get().error (and therefore invalid) stuck at the stale value, so the form never returns to a clean state. Include the initial error in the reset payload so the state realigns with a fresh form.

-      internalState.update(x =>
-        updateState(x, pick(initialState, ['isSubmitted']))
-      )
+      internalState.update(x =>
+        updateState(x, pick(initialState, ['error', 'isSubmitted']))
+      )

133-136: Array reset keeps stale validation errors.

Because the updateState call does not override error, any failure returned by field.validate persists after reset(), leaving state.get().invalid true and the array field permanently marked invalid. Add the initial error alongside children and isSubmitted when rebuilding the state.

-      const { children } = internalState.update(x =>
-        updateState(x, pick(initialState, ['children', 'isSubmitted']))
-      )
+      const { children } = internalState.update(x =>
+        updateState(x, pick(initialState, ['children', 'error', 'isSubmitted']))
+      )

202-205: Basic field reset leaves error and invalid latched.

After a validation failure, calling reset() should return the field to a clean slate. The current implementation keeps the previous error, so state.get().invalid never clears. Make sure we reset the error alongside value/flags.

-      internalState.update(x =>
-        updateState(x, pick(initialFormFieldState, ['value', 'isSubmitted', 'isVisited']))
-      )
+      internalState.update(x =>
+        updateState(x, pick(initialFormFieldState, ['error', 'value', 'isSubmitted', 'isVisited']))
+      )
🧹 Nitpick comments (1)
src/fields.js (1)

75-75: Rename local to avoid constructor shadowing.

Biome flags this identifier because constructor is a restricted global name; keeping it trips lint. Rename the local binding so the pipeline stays green.

-      const constructor = constructors[normalizedField.type]
-      return constructor({
+      const buildField = constructors[normalizedField.type]
+      return buildField({
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c544dc and a347e13.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (25)
  • .gitignore (1 hunks)
  • example/tsconfig.json (1 hunks)
  • package.json (1 hunks)
  • scripts/install-peer-dependencies-for-typescript.js (1 hunks)
  • scripts/tsconfig.json (1 hunks)
  • src/components.js (1 hunks)
  • src/components.type.test.ts (1 hunks)
  • src/fields.js (9 hunks)
  • src/fields.type.test.ts (1 hunks)
  • src/hooks.js (7 hunks)
  • src/hooks.type.test.ts (1 hunks)
  • src/normalize.js (2 hunks)
  • src/normalize.type.test.ts (1 hunks)
  • src/schema.js (2 hunks)
  • src/schema.type.test.ts (1 hunks)
  • src/snapshot.js (4 hunks)
  • src/snapshot.type.test.ts (1 hunks)
  • src/state.js (4 hunks)
  • src/type-helpers.js (1 hunks)
  • src/type.test.helpers.ts (1 hunks)
  • src/types.d.ts (0 hunks)
  • src/types.ts (1 hunks)
  • src/validation.js (1 hunks)
  • tsconfig.json (1 hunks)
  • types/global.d.ts (0 hunks)
💤 Files with no reviewable changes (2)
  • types/global.d.ts
  • src/types.d.ts
🧰 Additional context used
🧬 Code graph analysis (13)
src/components.type.test.ts (5)
src/hooks.js (2)
  • useForm (25-56)
  • form (31-35)
src/validation.js (1)
  • optionalT (22-24)
src/schema.js (2)
  • object (19-21)
  • array (39-41)
src/components.js (4)
  • FormFieldValue (13-16)
  • value (14-14)
  • FormFieldsValues (24-27)
  • FormFieldValid (35-38)
src/type.test.helpers.ts (4)
  • expectNotAny (12-12)
  • expectNotNever (2-2)
  • expectAssignable (15-15)
  • Prepared (28-29)
src/hooks.type.test.ts (6)
src/hooks.js (10)
  • useForm (25-56)
  • form (31-35)
  • useFormFieldValue (129-131)
  • useFormFieldsValues (137-139)
  • useFormField (145-151)
  • useNumberFormField (156-168)
  • useBooleanFormField (171-181)
  • useObjectFormField (198-203)
  • useArrayFormField (187-192)
  • useFormFieldSnapshot (113-122)
src/validation.js (1)
  • optionalT (22-24)
src/schema.js (2)
  • object (19-21)
  • array (39-41)
src/type.test.helpers.ts (4)
  • expectNotAny (12-12)
  • expectNotNever (2-2)
  • expectAssignable (15-15)
  • Prepared (28-29)
src/types.ts (9)
  • Field (162-162)
  • Basic (144-147)
  • Basic (203-213)
  • Basic (258-260)
  • Basic (298-299)
  • State (246-246)
  • Event (215-219)
  • Common (249-256)
  • Snapshot (289-289)
src/components.js (1)
  • useFormFieldSnapshot (36-36)
src/schema.type.test.ts (5)
src/type-helpers.js (1)
  • asConst (5-7)
src/validation.js (8)
  • optional (50-50)
  • optional (50-50)
  • email (83-85)
  • email (83-85)
  • required (51-53)
  • required (51-53)
  • number (55-57)
  • number (55-57)
src/types.ts (2)
  • Validate (23-27)
  • InitialValue (33-34)
src/schema.js (2)
  • object (19-21)
  • array (39-41)
src/type.test.helpers.ts (4)
  • expectNotAny (12-12)
  • expectAssignable (15-15)
  • Prepared (28-29)
  • expectNotNever (2-2)
src/normalize.js (1)
src/hooks.js (3)
  • field (147-147)
  • field (188-188)
  • field (199-199)
src/fields.type.test.ts (6)
src/type-helpers.js (1)
  • asConst (5-7)
src/normalize.js (1)
  • normalize (9-17)
src/schema.js (2)
  • object (19-21)
  • array (39-41)
src/fields.js (7)
  • validate (28-28)
  • validate (102-102)
  • validate (180-180)
  • createObjectFormField (21-83)
  • value (30-40)
  • value (104-118)
  • value (182-190)
src/types.ts (9)
  • ValidationContext (31-31)
  • State (246-246)
  • Readonly (268-271)
  • Basic (144-147)
  • Basic (203-213)
  • Basic (258-260)
  • Basic (298-299)
  • Field (162-162)
  • Event (215-219)
src/type.test.helpers.ts (4)
  • expectAssignable (15-15)
  • Prepared (28-29)
  • expectNotAny (12-12)
  • expectNotNever (2-2)
src/normalize.type.test.ts (5)
src/type-helpers.js (1)
  • asConst (5-7)
src/schema.js (2)
  • object (19-21)
  • array (39-41)
src/types.ts (1)
  • ValidationFunction (28-28)
src/normalize.js (1)
  • normalize (9-17)
src/type.test.helpers.ts (4)
  • expectNotAny (12-12)
  • expectNotNever (2-2)
  • expectAssignable (15-15)
  • Prepared (28-29)
src/components.js (2)
src/hooks.js (6)
  • useFormFieldValue (129-131)
  • field (147-147)
  • field (188-188)
  • field (199-199)
  • useFormFieldsValues (137-139)
  • useFormFieldSnapshot (113-122)
src/snapshot.js (3)
  • field (49-49)
  • field (75-75)
  • field (100-100)
src/types.ts (2)
src/components.js (1)
  • value (14-14)
src/fields.js (3)
  • value (30-40)
  • value (104-118)
  • value (182-190)
src/fields.js (2)
src/state.js (3)
  • subscribeToChildren (79-91)
  • createState (8-28)
  • state (9-9)
src/normalize.js (1)
  • normalize (9-17)
src/schema.js (1)
src/fields.js (1)
  • fields (23-23)
src/snapshot.type.test.ts (6)
src/hooks.js (2)
  • useForm (25-56)
  • form (31-35)
src/validation.js (2)
  • optionalT (22-24)
  • requiredT (46-48)
src/schema.js (2)
  • object (19-21)
  • array (39-41)
src/type-helpers.js (1)
  • asConst (5-7)
src/type.test.helpers.ts (4)
  • expectNotAny (12-12)
  • expectNotNever (2-2)
  • expectAssignable (15-15)
  • Prepared (28-29)
src/types.ts (2)
  • Falsy (15-15)
  • ValidationError (30-30)
src/snapshot.js (1)
src/state.js (1)
  • subscribeToChildren (79-91)
src/hooks.js (2)
example/src/domain/Full.js (5)
  • useForm (58-64)
  • field (207-207)
  • field (215-215)
  • field (223-223)
  • fields (12-48)
src/type-helpers.js (1)
  • asAny (10-12)
🪛 Biome (2.1.2)
src/components.type.test.ts

[error] 7-7: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

src/hooks.type.test.ts

[error] 17-17: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 57-57: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 60-60: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 63-63: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 77-77: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 91-91: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 105-105: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 119-119: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 144-144: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

src/fields.js

[error] 74-74: Do not shadow the global "constructor" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

src/snapshot.type.test.ts

[error] 9-9: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

src/hooks.js

[error] 10-10: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 25-25: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

@EECOLOR
Copy link
Member Author

EECOLOR commented Nov 8, 2025

Note to self: this is a major version update, see the code rabbit comment about state (notify used to receive both newState and oldState)

@EECOLOR
Copy link
Member Author

EECOLOR commented Nov 8, 2025

@coderabbitai about the out-of-range comments concerning reset. I think this line in the hooks.js file (around line 37) resolves that problem: form.value.subscribe(value => form.validate({ form: value, parents: [] }))

@coderabbitai
Copy link

coderabbitai bot commented Nov 8, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the hooks.js file and show the relevant section around line 37
cat -n src/hooks.js | sed -n '25,50p'

Length of output: 1282


🏁 Script executed:

#!/bin/bash
# Look for the handleReset function implementation
cat -n src/hooks.js | sed -n '40,80p'

Length of output: 1177


🏁 Script executed:

#!/bin/bash
# Search for reset method implementation in fields.js
rg -n "reset" src/fields.js -A5 -B2

Length of output: 1067


@EECOLOR You're absolutely right! The subscription on line 37 ensures that validation is automatically triggered whenever the form value changes, including when reset() is called.

Here's the flow:

  1. reset() is called → triggers state update in fields.js (lines 201-205 for basic fields, restoring initial values)
  2. State update → triggers value change
  3. Value change → triggers the subscription: form.value.subscribe(value => form.validate({ form: value, parents: [] }))
  4. Validation runs automatically

This is a clean reactive design pattern where validation is consistently applied to all value changes regardless of the source (user input, programmatic updates, or reset). The subscription established during form initialization ensures validation stays in sync with the form state.

Thank you for the clarification!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
package.json (1)

28-31: Good conditional postinstall implementation!

The postinstall script now correctly handles the case where the script file doesn't exist, preventing failures in consumer installs. The test.types script properly references the new type-test configuration.

One minor observation: the trailing semicolon after fi on line 29 is unnecessary (though harmless). Consider:

-    "postinstall": "if test -f ./scripts/install-peer-dependencies-for-typescript.js; then node ./scripts/install-peer-dependencies-for-typescript.js; fi;",
+    "postinstall": "if test -f ./scripts/install-peer-dependencies-for-typescript.js; then node ./scripts/install-peer-dependencies-for-typescript.js; fi",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a347e13 and f78c062.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (6)
  • package.json (1 hunks)
  • src/fields.js (9 hunks)
  • src/fields.type.test.ts (1 hunks)
  • src/hooks.js (7 hunks)
  • src/types.ts (1 hunks)
  • tsconfig.type.test.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/fields.js
🧰 Additional context used
🧬 Code graph analysis (3)
src/fields.type.test.ts (6)
src/type-helpers.js (1)
  • asConst (5-7)
src/normalize.js (1)
  • normalize (9-17)
src/schema.js (2)
  • object (19-21)
  • array (39-41)
src/fields.js (7)
  • validate (28-28)
  • validate (102-102)
  • validate (180-180)
  • createObjectFormField (21-83)
  • value (30-40)
  • value (104-118)
  • value (182-190)
src/types.ts (9)
  • ValidationContext (31-31)
  • State (246-246)
  • Readonly (268-271)
  • Basic (144-147)
  • Basic (203-213)
  • Basic (258-260)
  • Basic (298-299)
  • Field (162-162)
  • Event (215-219)
src/type.test.helpers.ts (4)
  • expectAssignable (15-15)
  • Prepared (28-29)
  • expectNotAny (12-12)
  • expectNotNever (2-2)
src/types.ts (2)
src/fields.js (3)
  • value (30-40)
  • value (104-118)
  • value (182-190)
src/components.js (1)
  • value (14-14)
src/hooks.js (1)
src/type-helpers.js (1)
  • asAny (10-12)
🪛 Biome (2.1.2)
src/hooks.js

[error] 10-10: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 25-25: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🔇 Additional comments (13)
tsconfig.type.test.json (1)

1-6: LGTM!

The TypeScript configuration correctly extends the base config and appropriately narrows the scope to type-test files. This setup aligns well with the new test.types script in package.json.

package.json (1)

32-42: LGTM!

The peer dependencies and dev dependencies are appropriate for adding TypeScript support:

  • React 18.3.1 aligns with modern React usage
  • TypeScript 5.8.2 and corresponding @types packages are recent and compatible
  • The setup supports both runtime (peerDependencies) and development-time (devDependencies) typing needs
src/fields.type.test.ts (1)

1-135: LGTM!

The type-level tests are comprehensive and correctly validate:

  • Basic field types with different validation configurations (optional, single validation, multiple validations)
  • Object field composition and value aggregation
  • Nested structures with both object and array children (correctly including both in the expected value type at line 127)
  • Event handler signatures and state management types

The tests provide strong type-safety guarantees for the form field system.

src/hooks.js (6)

12-56: LGTM!

The useForm hook now has comprehensive TypeScript generics and JSDoc annotations. Key improvements:

  • Proper template parameters for fields, initial values, and validation
  • Correct type reference Field.ObjectFromObjectInput<A> on line 27 (previous typo resolved)
  • The form initialization and subscription setup on lines 29-39 correctly triggers validation on value changes

Note: The static analysis warning about line 25 is a false positive—useForm is the hook declaration itself, not a conditional hook call.


58-106: LGTM!

The state management hooks are well-typed:

  • useFormFieldState properly extracts the inner value type from State.Readonly<T>
  • useFieldStates correctly handles tuple typing with StateTupleToValueTuple cast on line 104
  • The dependency array on line 98 appropriately passes the states array directly, as explained in the comment block at lines 205-214

108-131: LGTM!

The snapshot and value hooks are properly typed:

  • useFormFieldSnapshot constructs a memoized state object with @satisfies State.Readonly annotation ensuring type correctness
  • useFormFieldValue cleanly delegates to useFormFieldState
  • Return types are accurately derived from the field types

133-139: LGTM!

The useFormFieldsValues hook correctly maps field values with proper tuple typing via the MapTuple<T, 'value'> cast on line 138, preserving tuple structure and element types.


141-181: LGTM!

The form field hooks are well-implemented with proper type annotations:

  • useFormField provides clean extraction of field properties
  • useNumberFormField correctly handles number conversion, including edge cases (empty string, NaN)
  • useBooleanFormField properly extracts the checked property
  • Event handlers are correctly typed with React.ChangeEvent<HTMLInputElement>

183-203: LGTM!

The composite field hooks are properly implemented:

  • useArrayFormField returns helpers (add/remove operations) appropriate for array manipulation
  • useObjectFormField returns fields (child form fields) appropriate for object structure
  • Both hooks are correctly typed with their respective generics (Field.Array and Field.Object)

The semantic distinction between returning helpers for arrays and fields for objects aligns well with their different usage patterns.

src/types.ts (4)

1-34: LGTM!

The utility types are well-designed and serve clear purposes:

  • Expand improves IDE type hint readability (lines 5-6)
  • PartialWithStringKey handles TypeScript's keyof string constraints (lines 12-13)
  • MapTuple provides tuple-preserving mapping (lines 18-21)
  • Validation types (Validate, ValidationFunction, ValidationError, ValidationContext) comprehensively model the validation pipeline

36-92: LGTM!

The FieldInput and FieldSchema namespaces provide robust type mappings:

  • FieldInput properly bridges to FieldSchema values (lines 38-42)
  • FieldSchema.ToValue handles all schema variants through conditional types (lines 58-63)
  • Object and Array schema types are well-defined with proper field and validation typing
  • ExtractObjectFields correctly handles both direct object fields and constructor patterns (lines 71-74)

94-244: LGTM!

The NormalizedField and Field namespaces provide comprehensive runtime field modeling:

  • NormalizedField.FromFieldSchema correctly transforms all schema variants (lines 103-136)
  • ValidateToValidationFunction properly handles null/undefined/Validate cases
  • Field types accurately model runtime structure with state, event handlers, and helpers
  • Line 174 correctly uses ObjectFromObjectInput (previous typo concern resolved)
  • BaseFieldProperties is well-extracted for reuse across field types (lines 195-201)
  • Event types properly model both direct values and event objects (lines 215-219)

246-326: LGTM!

The State and Snapshot namespaces complete the type system:

  • State.Common defines shared state properties (error, validity, focus, etc.) at lines 249-256
  • Clear distinction between Readonly (single value subscription) and ReadonlyWithHistory (with old value) at lines 268-275
  • Subscription types properly handle both variants (lines 279-280)
  • StateTupleToValueTuple enables type-preserving tuple mapping (lines 283-286)
  • Snapshot types correctly model immutable submission data with nested value/error structure
  • MapToValues and MapToErrors provide comprehensive field-to-value/error mapping (lines 321-325)

- More precise typing for error and related booleans
- Partial is now recursive
- Additional utility type to more easily get the form shape from fields
- Export new validation functions
- Export types
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
example/src/domain/Full.js (2)

58-85: Don’t discard the submitted form data.

Casting snapshot.value to a boolean means we throw away the actual submission payload, so the thank-you view just renders true instead of the form contents. The example relied on showing the captured values. Keep storing the snapshot value (and type it accordingly) rather than collapsing it to a flag; the existing submitted ? … : … guard still works because any non-null object is truthy.

-  const [submitted, setSubmitted] = React.useState(/** @type {boolean | null} */ (null))
+  const [submitted, setSubmitted] = React.useState(
+    /** @type {Snapshot.FromObjectFields<typeof fields>['value'] | null} */ (null)
+  )-    setSubmitted(Boolean(snapshot.value))
+    setSubmitted(snapshot.value)

Remember to update Bedankt’s prop type to expect the same value shape.


168-176: Align Bedankt with the restored submission value.

Once submitted holds the actual snapshot data again, this prop typing must reflect that; otherwise we’re still treating it as a boolean and the example output stays wrong.

-/** @arg {{ submitted: boolean, onReset: () => void }} props */
+/** @arg {{ submitted: Snapshot.FromObjectFields<typeof fields>['value'], onReset: () => void }} props */
 function Bedankt({ submitted, onReset }) {
…
-      <Code value={submitted} indent />
+      <Code value={submitted} indent />
example/src/domain/Basic.js (1)

41-54: Fix typo in error message.

Line 51 contains a typo: 'unkown error' should be 'unknown error'.

Apply this diff to fix the typo:

-      {showError && <p>{hasKey(validationErrors, error.id) ? validationErrors[error.id] : 'unkown error'}</p>}
+      {showError && <p>{hasKey(validationErrors, error.id) ? validationErrors[error.id] : 'unknown error'}</p>}
♻️ Duplicate comments (3)
tsconfig.json (1)

24-26: Restore module resolution for peer types.

Mapping "*" solely to "./typescript/*" still prevents the compiler from finding react, @types/react, etc.—those packages are installed in typescript/node_modules, not at typescript/<name>, so every bare import resolves to a non-existent path. Please reinstate the node_modules entries (per the earlier feedback) so TypeScript can see the installed peers.

-    "paths": {
-      "*": ["./typescript/*"]
-    }
+    "paths": {
+      "*": [
+        "./typescript/node_modules/*",
+        "./typescript/*",
+        "*"
+      ]
+    }
package.json (1)

28-31: Fix the postinstall guard — it still breaks installs.

if test -f … is POSIX-only and returns exit code 1 when the script is absent, so consumers on Unix still get a failing postinstall, and Windows installs explode outright because test/that shell syntax doesn’t exist. We need a cross-platform check that always exits 0 when the helper script isn’t present (and ideally ship the script, or guard inside it). One workable option:

-    "postinstall": "if test -f ./scripts/install-peer-dependencies-for-typescript.js; then node ./scripts/install-peer-dependencies-for-typescript.js; fi",
+    "postinstall": "node -e \"const fs=require('fs'); const path=require('path'); const { execFileSync } = require('child_process'); const script=path.join(__dirname,'scripts','install-peer-dependencies-for-typescript.js'); if (!fs.existsSync(script)) process.exit(0); execFileSync(process.execPath, [script], { stdio: 'inherit' });\"",

(Alternatively, include scripts/install-peer-dependencies-for-typescript.js in the files array and perform the existence check at the top of that script.)

src/hooks.js (1)

26-26: Fix typo in Field type alias.

Field.ObjecFromObjectInput is misspelled (missing the 't'). This should be Field.ObjectFromObjectInput to match the type defined in src/types.ts.

Apply this diff to fix the typo:

-  const formRef = React.useRef(/** @type {Field.ObjecFromObjectInput<A>} */ (asAny(null)))
+  const formRef = React.useRef(/** @type {Field.ObjectFromObjectInput<A>} */ (asAny(null)))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f78c062 and f30e648.

⛔ Files ignored due to path filters (2)
  • example-firebase/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • example/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (55)
  • example-firebase/.editorconfig (0 hunks)
  • example-firebase/.eslintrc (0 hunks)
  • example-firebase/.gitignore (0 hunks)
  • example-firebase/.stylelintrc (0 hunks)
  • example-firebase/bin/postinstall (0 hunks)
  • example-firebase/config/default.js (0 hunks)
  • example-firebase/config/dev.js (0 hunks)
  • example-firebase/config/firebase-database-rules.js (0 hunks)
  • example-firebase/config/firebase-storage.rules (0 hunks)
  • example-firebase/package.json (0 hunks)
  • example-firebase/scripts/deploy-database-rules.js (0 hunks)
  • example-firebase/scripts/deploy-storage-rules.js (0 hunks)
  • example-firebase/scripts/machinery/run.js (0 hunks)
  • example-firebase/scripts/readme.md (0 hunks)
  • example-firebase/services/form-processing-service.js (0 hunks)
  • example-firebase/services/machinery/createFirebaseServiceApp.js (0 hunks)
  • example-firebase/services/machinery/sendEmail.js (0 hunks)
  • example-firebase/services/machinery/serviceApplication.js (0 hunks)
  • example-firebase/services/readme.md (0 hunks)
  • example-firebase/src/cssGlobal/color.css (0 hunks)
  • example-firebase/src/cssGlobal/media.css (0 hunks)
  • example-firebase/src/cssGlobal/type.css (0 hunks)
  • example-firebase/src/domain/App.js (0 hunks)
  • example-firebase/src/domain/Firebase.js (0 hunks)
  • example-firebase/src/index.css (0 hunks)
  • example-firebase/src/index.html.js (0 hunks)
  • example-firebase/src/machinery/readme.md (0 hunks)
  • example-firebase/src/reset.css (0 hunks)
  • example-firebase/src/robots.txt (0 hunks)
  • example-firebase/tsconfig.json (0 hunks)
  • example-firebase/types/global.d.ts (0 hunks)
  • example/.gitignore (1 hunks)
  • example/README.md (1 hunks)
  • example/bin/postinstall (0 hunks)
  • example/config/default.js (1 hunks)
  • example/package.json (1 hunks)
  • example/src/cssGlobal/color.css (0 hunks)
  • example/src/cssGlobal/media.css (0 hunks)
  • example/src/cssGlobal/type.css (0 hunks)
  • example/src/domain/Basic.js (4 hunks)
  • example/src/domain/Full.js (7 hunks)
  • example/src/domain/machinery/Form.js (4 hunks)
  • example/src/domain/machinery/validation.js (1 hunks)
  • example/src/index.css (1 hunks)
  • example/tsconfig.json (1 hunks)
  • package.json (1 hunks)
  • src/fields.js (8 hunks)
  • src/fields.type.test.ts (1 hunks)
  • src/hooks.js (7 hunks)
  • src/normalize.js (2 hunks)
  • src/types.ts (1 hunks)
  • src/validation.js (1 hunks)
  • tsconfig.json (1 hunks)
  • types.ts (1 hunks)
  • validation.js (1 hunks)
💤 Files with no reviewable changes (35)
  • example-firebase/src/cssGlobal/type.css
  • example-firebase/src/reset.css
  • example-firebase/scripts/machinery/run.js
  • example-firebase/services/readme.md
  • example-firebase/src/machinery/readme.md
  • example-firebase/services/machinery/serviceApplication.js
  • example-firebase/src/index.html.js
  • example-firebase/types/global.d.ts
  • example/src/cssGlobal/type.css
  • example-firebase/src/cssGlobal/media.css
  • example-firebase/.gitignore
  • example-firebase/.editorconfig
  • example-firebase/bin/postinstall
  • example-firebase/package.json
  • example-firebase/scripts/deploy-storage-rules.js
  • example-firebase/src/index.css
  • example-firebase/services/machinery/sendEmail.js
  • example/src/cssGlobal/media.css
  • example-firebase/config/dev.js
  • example-firebase/config/default.js
  • example-firebase/src/domain/Firebase.js
  • example-firebase/.stylelintrc
  • example-firebase/config/firebase-database-rules.js
  • example-firebase/.eslintrc
  • example/src/cssGlobal/color.css
  • example-firebase/src/cssGlobal/color.css
  • example-firebase/scripts/deploy-database-rules.js
  • example-firebase/src/robots.txt
  • example-firebase/services/form-processing-service.js
  • example-firebase/scripts/readme.md
  • example-firebase/services/machinery/createFirebaseServiceApp.js
  • example-firebase/tsconfig.json
  • example-firebase/config/firebase-storage.rules
  • example/bin/postinstall
  • example-firebase/src/domain/App.js
✅ Files skipped from review due to trivial changes (4)
  • example/.gitignore
  • example/README.md
  • src/fields.type.test.ts
  • example/src/domain/machinery/Form.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • example/tsconfig.json
  • src/validation.js
🧰 Additional context used
🧬 Code graph analysis (6)
example/src/domain/Basic.js (1)
src/validation.js (2)
  • requiredT (52-54)
  • error (99-99)
src/hooks.js (2)
src/type-helpers.js (1)
  • asAny (10-12)
src/fields.js (4)
  • fields (23-23)
  • value (30-40)
  • value (104-118)
  • value (182-190)
example/src/domain/machinery/validation.js (2)
src/validation.js (1)
  • error (99-99)
src/hooks.js (1)
  • form (30-34)
src/fields.js (2)
src/state.js (3)
  • subscribeToChildren (79-91)
  • createState (8-28)
  • state (9-9)
src/normalize.js (1)
  • normalize (9-17)
example/src/domain/Full.js (2)
src/type-helpers.js (1)
  • asConst (5-7)
src/validation.js (5)
  • optionalT (22-24)
  • required (57-59)
  • required (57-59)
  • error (99-99)
  • requiredT (52-54)
src/types.ts (1)
src/fields.js (3)
  • value (30-40)
  • value (104-118)
  • value (182-190)
🪛 Biome (2.1.2)
src/hooks.js

[error] 10-10: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 24-24: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🔇 Additional comments (24)
example/src/index.css (1)

13-16: Typography tokens look good.

Using literal values keeps the previous defaults intact, so the example’s baseline styling remains consistent.

types.ts (1)

1-1: LGTM!

Clean barrel export pattern for the public types API.

validation.js (1)

1-7: LGTM!

The addition of requiredT and optionalT exports expands the validation API surface while maintaining backward compatibility.

example/src/domain/Basic.js (3)

1-23: LGTM!

The updated imports and field definitions properly integrate with the new type system. The use of requiredT('string') enables type-safe validation for the name field.


34-38: LGTM!

The JSDoc annotation properly documents the snapshot parameter.


56-59: LGTM!

The hasKey type guard helper is correctly implemented with a proper type predicate, enabling type-safe access to validation error messages.

example/src/domain/machinery/validation.js (2)

1-20: LGTM!

The type annotations for ifFormHasValue properly constrain the function with template parameters while preserving runtime behavior.


22-36: LGTM!

The type annotations for ifParentHasValue correctly document the template-based validation function composition pattern.

src/normalize.js (3)

1-17: LGTM!

The generic template parameter and type cast enable proper type inference for normalized fields while maintaining runtime behavior.


19-66: LGTM!

The type annotations for all conversion helper functions properly document their input/output contracts and return types.


68-105: LGTM!

The enhanced toValidationFunction properly handles the validation function composition with explicit null handling and type annotations.

src/fields.js (4)

1-83: LGTM!

The createObjectFormField function is comprehensively typed with template parameters. The @satisfies annotation on the value getter (lines 30-40) ensures type safety while the runtime behavior remains unchanged.


85-166: LGTM!

The createArrayFormField function maintains runtime behavior while adding comprehensive type annotations. The State.Readonly wrapper on lines 104-118 properly types the array value state.


168-224: LGTM!

The createBasicFormField implementation properly uses State.ReadonlyWithHistory (lines 182-190) to type the value state, preserving the history-aware subscription semantics.


226-321: LGTM!

The helper functions (updateState, pick, deriveFormFieldState, mapValues, bindValidate, addParent) are properly typed with comprehensive JSDoc annotations. The ts-expect-error comments appropriately mark locations where TypeScript's inference falls short for complex reduce operations.

src/hooks.js (3)

12-55: LGTM!

The useForm hook is properly typed with template parameters for fields and validation. The implementation correctly maintains form state and validation subscriptions (line 36).


57-139: LGTM!

The state and value hooks (useFormFieldState, useFieldStates, useFormFieldSnapshot, useFormFieldValue, useFormFieldsValues) are properly typed with comprehensive template parameters and return types.


141-202: LGTM!

The specialized field hooks (useFormField, useNumberFormField, useBooleanFormField, useArrayFormField, useObjectFormField) properly implement type-safe event handling and state management.

src/types.ts (6)

1-47: LGTM!

The foundational type utilities (Expand, PartialWithStringKey, Falsy, MapTuple) and validation types (Validate, ValidationFunction, ValidationContext) provide a solid base for the type system. The InitialValue type properly handles partial nested structures.


48-104: LGTM!

The FieldInput and FieldSchema namespaces properly model the input and normalized schema representations with appropriate value extraction utilities.


106-172: LGTM!

The NormalizedField namespace correctly models normalized field structures with proper validation function typing and value extraction.


174-253: LGTM!

The Field namespace comprehensively models runtime field instances with proper type mappings. Note that ObjectFromObjectInput on line 186 is correctly spelled (with the 't'), but its usage in src/hooks.js still has the typo.


255-305: LGTM!

The State namespace properly models form field state with discriminated unions for valid/invalid states and comprehensive readonly/readwrite state wrappers.


307-347: LGTM!

The Snapshot namespace correctly models snapshot representations with proper value and error mappings for all field types.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
example/src/domain/Full.js (2)

81-85: Consider removing redundant Boolean cast.

The snapshot.value is already typed correctly from the Snapshot.FromObjectFields type, so Boolean(snapshot.value) on line 84 appears redundant. The value should already be properly typed.

Apply this diff if you want to remove the cast:

  function handleSubmit(snapshot) {
    if (snapshot.invalid) return
-    setSubmitted(Boolean(snapshot.value))
+    setSubmitted(snapshot.value)
  }

255-259: Consider importing from shared location.

The asConst and throwError helpers are duplicated here but also exist in src/type-helpers.js (visible in the relevant code snippets). Consider importing these from the shared module to avoid duplication and maintain a single source of truth.

Apply this diff to import instead of redefining:

+import { asConst, throwError } from './path/to/type-helpers'
+
 // ... rest of file ...

-/** @template const T @arg {T} x */
-function asConst(x) { return x }
-
-/** @arg {string} message @returns {never} */
-function throwError(message) { throw new Error(message) }

Note: Adjust the import path based on the actual location of src/type-helpers.js relative to this example file.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f30e648 and 137393c.

📒 Files selected for processing (6)
  • example/src/domain/Full.js (6 hunks)
  • example/src/domain/machinery/Code.js (1 hunks)
  • example/src/domain/machinery/Form.js (10 hunks)
  • example/tsconfig.json (1 hunks)
  • example/types/global.d.ts (0 hunks)
  • src/validation.js (1 hunks)
💤 Files with no reviewable changes (1)
  • example/types/global.d.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • example/tsconfig.json
  • example/src/domain/machinery/Form.js
🧰 Additional context used
🧬 Code graph analysis (1)
example/src/domain/Full.js (3)
src/validation.js (8)
  • optionalT (25-27)
  • required (40-42)
  • required (40-42)
  • email (72-74)
  • email (72-74)
  • requiredT (35-37)
  • error (82-82)
  • minLength (61-64)
src/type-helpers.js (1)
  • asConst (5-7)
src/snapshot.js (4)
  • field (49-49)
  • field (75-75)
  • field (100-100)
  • children (76-86)
🔇 Additional comments (10)
example/src/domain/machinery/Code.js (1)

1-1: ****

The original review comment is based on incorrect information. TypeScript recognizes @arg as a valid JSDoc tag—it is an alias of @param and treated identically for JSDoc-based type information.

The Code.js annotation is correct and follows the project's consistent convention, with 82+ instances of @arg throughout the codebase. No changes are needed.

Likely an incorrect or invalid review comment.

src/validation.js (3)

29-37: LGTM! Type-level helper with runtime consistency.

The requiredT function correctly returns the required validator while providing compile-time type information through the type parameter. The pattern is consistent with optionalT.


3-13: LGTM! Type mapping with reasonable coverage.

The TypeLookup typedef correctly maps string literals to their corresponding types with proper support for primitives and arrays. The recursive array pattern and fallback case are well-designed.


40-74: LGTM! Comprehensive type annotations.

The type annotations added to all validators are accurate and provide proper type safety:

  • Generic template on required for flexible input types
  • Specific type constraints on numeric and string validators
  • Structural typing for minLength/maxLength supporting any object with a length property
example/src/domain/Full.js (6)

14-47: LGTM! Consistent migration to typed validators.

The field definitions correctly migrate from untyped to typed validators:

  • optionalT('string') provides compile-time type information while maintaining the same runtime behavior (null)
  • requiredT variants preserve the required validator behavior
  • asConst wrapper correctly preserves literal types for TypeScript inference
  • The dynamic schema in specialeToevoeging (lines 40-44) is well-designed with runtime validation via throwError

49-49: LGTM! Proper type derivation from fields.

The FormType typedef correctly derives the form type from the fields object using Field.ObjectFromObjectInput, providing end-to-end type safety.


93-98: LGTM! Proper component props typing.

The JSDoc type annotations for the Formulier component props are accurate and provide good IDE support.


152-159: LGTM! Correct discriminated union handling.

The use of asConst for initialValue preserves literal types, and the 'roodInfo' in fields / 'groenInfo' in fields checks correctly discriminate between the union types at runtime.


168-184: LGTM! Proper helper component typing.

Both Bedankt and Conditional components have accurate JSDoc type annotations. The use of Field.Basic<boolean> for the Conditional field prop is appropriate.


186-253: LGTM! Comprehensive typing for field state helpers.

The type annotations for useSendSignalWhenIsVisited and the getIsVisited helper functions are thorough and accurate. The @ts-expect-error on line 219 is necessary because TypeScript cannot verify the dynamic field.type lookup is exhaustive, even though the runtime logic correctly handles all field types.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
example/tsconfig.json (1)

29-29: Consider reducing maxNodeModuleJsDepth.

Setting maxNodeModuleJsDepth to 10 is unusually high (default is 0). This instructs TypeScript to analyze JavaScript in node_modules up to 10 levels deep, which may impact type-checking performance without providing significant benefit, since dependencies typically ship their own types.

Consider reverting to a lower value (e.g., 1 or omitting it) unless there is a specific reason to deeply analyze dependency sources.

example/src/domain/Full.js (1)

255-259: Consider importing asConst and throwError from the library.

These utility functions are redefined locally but appear to be available in the library's type-helpers module (as seen in src/type-helpers.js). To reduce code duplication and follow DRY principles, consider importing them instead:

+import { asConst } from '@kaliber/forms/type-helpers'
+
 /**
  * When you study this example, don't forget to check the components in the machinery directory
  */

Then remove the local definitions:

-
-/** @template const T @arg {T} x */
-function asConst(x) { return x }
-
-/** @arg {string} message @returns {never} */
-function throwError(message) { throw new Error(message) }

If throwError is not exported from the library, you could keep that one local while importing asConst.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 415bc9b and e73a1c9.

⛔ Files ignored due to path filters (1)
  • example/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • example/package.json (1 hunks)
  • example/src/domain/Full.js (6 hunks)
  • example/tsconfig.json (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
example/src/domain/Full.js (7)
src/fields.js (5)
  • fields (23-23)
  • value (30-40)
  • value (104-118)
  • value (182-190)
  • children (24-24)
src/validation.js (8)
  • optionalT (25-27)
  • required (40-42)
  • required (40-42)
  • email (72-74)
  • email (72-74)
  • requiredT (35-37)
  • error (82-82)
  • minLength (61-64)
example/src/domain/machinery/validation.js (4)
  • date (5-5)
  • date (5-5)
  • ifParentHasValue (30-36)
  • ifFormHasValue (15-20)
src/type-helpers.js (1)
  • asConst (5-7)
src/hooks.js (5)
  • form (30-34)
  • useFormFieldValue (128-130)
  • field (146-146)
  • field (187-187)
  • field (198-198)
src/components.js (1)
  • FormFieldValid (35-38)
src/snapshot.js (4)
  • field (49-49)
  • field (75-75)
  • field (100-100)
  • children (76-86)
🔇 Additional comments (14)
example/tsconfig.json (3)

17-18: Verify strict type checking on existing JavaScript files.

The combination of checkJs: true, noImplicitAny: true, and strictNullChecks: true will enforce strict type checking on all JavaScript files in the example folder (src, config). Existing JS code without type annotations may fail these checks.

Confirm that existing example code (if any) has been audited or updated to satisfy these stricter constraints, or consider relaxing these flags for .js files if necessary.


2-5: Good: typeAcquisition correctly excludes the local library.

Excluding @kaliber/forms from type acquisition is correct, since the library's types are now provided locally in this PR rather than from DefinitelyTyped.


6-7: TypeScript configuration aligns with PR's type system introduction.

The configuration updates support the new TypeScript types:

  • Paths and module resolution (node16) align with the PR's typing strategy.
  • allowImportingTsExtensions and noEmit allow type-checking .ts files without runtime concerns.
  • allowJs, checkJs, and strict null/any checks enforce type safety across the example codebase.

Also applies to: 9-10, 15-16, 22-24

example/package.json (1)

8-9: NODE_OPTIONS flag addition is appropriate for symlinked peer dependencies.

The --preserve-symlinks option aligns with the postinstall peer dependency installation into a typescript/ folder (as described in the PR summary). This ensures the build and serve processes correctly resolve symlinked dependencies.

example/src/domain/Full.js (10)

2-2: LGTM! Type helper imports enhance type safety.

The additions of optionalT and requiredT validation helpers along with the JSDoc type imports for Field and Snapshot properly support the TypeScript typing additions throughout this example.

Also applies to: 7-7


14-14: LGTM! Field type annotations are correctly applied.

The typed validation helpers (optionalT, requiredT) are used appropriately for their respective field types, and the JSDoc parameter annotation on line 45 adds useful type safety to the custom validator.

Also applies to: 17-18, 37-37, 45-45


19-27: LGTM! The asConst wrappers properly preserve literal types.

Wrapping the object field definitions with asConst enables TypeScript to infer more precise literal types for heterogeneous structures, which is essential for type-safe form handling.

Also applies to: 28-36


48-49: LGTM! Type derivation from fields definition is elegant.

Using Field.ObjectFromObjectInput<typeof fields> to derive FormType provides a single source of truth for the form structure, improving type safety and maintainability.


57-58: LGTM! Snapshot type derivation ensures type safety.

The type annotations correctly derive snapshot types from FormType, ensuring that submitted and the handleSubmit parameter are properly typed according to the form structure.

Also applies to: 81-81


93-98: LGTM! Component prop types are comprehensive.

The JSDoc type annotations for component props provide clear type contracts and enable type checking for Formulier, Bedankt, and Conditional components.

Also applies to: 168-168, 180-180


152-158: LGTM! Type discrimination using the in operator is type-safe.

Using the in operator to check for the presence of roodInfo or groenInfo is the idiomatic TypeScript pattern for discriminating union types at runtime. Combined with the asConst-wrapped initialValue objects, this provides both type safety and correct runtime behavior.


162-162: LGTM! Empty string for cursor is cleaner.

Using an empty string for the cursor style when the form is valid allows the browser's default cursor to apply naturally, which is cleaner than explicitly setting 'default'.


186-189: LGTM! JSDoc annotations improve helper function type safety.

The comprehensive JSDoc type annotations for useSendSignalWhenIsVisited, getIsVisited, and related helper functions provide clear documentation and enable type checking for these utility functions.

Also applies to: 210-213, 223-225, 229-229, 235-237, 241-241, 247-249


219-220: LGTM! The @ts-expect-error suppression is justified.

TypeScript cannot always narrow discriminated union types when using the discriminator as a dynamic object key. The @ts-expect-error directive appropriately suppresses this limitation while the runtime behavior remains correct.

@EECOLOR
Copy link
Member Author

EECOLOR commented Nov 10, 2025

De suggesties zijn verwerkt en ook de example maakt nu gebruik van de typed versie en de nieuwe @kaliber/build.

Laatste stap voor release: package maken (tarball) en in de node_modules map van een project zetten. Let op: grote kans dat je de maxNodeModuleJsDepth setting nodig hebt.

@EECOLOR
Copy link
Member Author

EECOLOR commented Nov 13, 2025

Ik heb de tarball getest en dat werkt :-D Dus wat mij betreft kan die gereleased worden. Is wel een major version release.

@AlbertSmit en @buildersteven4, waneer denken jullie even een blik te kunnen werpen?

@socket-security
Copy link

socket-security bot commented Dec 23, 2025

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm safer-buffer is 94.0% likely obfuscated

Confidence: 0.94

Location: Package overview

From: ?npm/@kaliber/build@3.1.0npm/@kaliber/serve@1.0.5npm/safer-buffer@2.1.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/safer-buffer@2.1.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/validation.js (1)

39-51: Previously discussed — design confirmed by author.

The overload signature returning never was flagged in a prior review, and the author confirmed this is intentional for type-level inference purposes.

🧹 Nitpick comments (1)
src/validation.js (1)

30-37: Type-level helper returning null — document the intended usage.

withT returns null at runtime but declares Constructable<T> as its return type. This is a valid pattern for type-level helpers where the return value is only used for type inference. Consider adding a brief JSDoc comment explaining this is a type-only helper to prevent confusion for library consumers.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e73a1c9 and 826d2a8.

📒 Files selected for processing (3)
  • src/types.ts
  • src/validation.js
  • validation.js
🔇 Additional comments (16)
validation.js (1)

1-7: LGTM!

The expanded exports cleanly expose the new typed variants (requiredT, optionalT, withT) alongside the existing validators. This is a straightforward, additive API surface change.

src/validation.js (4)

18-28: TypeLookup only splits on first | — nested unions may not parse correctly.

The template literal pattern `${infer A} | ${infer B}` matches only the first | occurrence. For types like 'string | number | boolean', this would infer A = 'string' and B = 'number | boolean', then recursively call TypeLookup<'number | boolean'>. This works due to recursion, but the pattern relies on left-to-right splitting.

This is acceptable if intentional, but be aware that deeply nested unions will recurse multiple times. Consider documenting this behavior if consumers will use complex union strings.


53-61: LGTM!

requiredT correctly wraps the existing required validator with proper type inference based on the provided type parameter.


63-98: LGTM!

The JSDoc type annotations for all validators (required, number, min, max, minLength, maxLength, email) are correctly applied and accurately describe the expected parameter types.


100-106: LGTM!

The updated template using {[...any]} P for rest parameters correctly captures variadic arguments as a tuple type, improving type inference for error parameters.

src/types.ts (11)

5-6: LGTM!

The Expand utility type is a standard and effective pattern for improving type readability in IDE tooltips by flattening intersection types.


12-22: LGTM!

PartialWithStringKey correctly handles recursive partial mapping for objects and arrays while ensuring keys remain strings. The nested conditional logic properly distinguishes between array and object types.


26-30: LGTM!

MapTuple correctly implements recursive tuple mapping with proper inference and rest element handling.


32-47: LGTM!

The validation type hierarchy is well-structured. ValidateToValue correctly handles various validation function arrangements (single, tuple, null) with proper type extraction and intersection for multiple validators.


55-67: LGTM!

The FieldInput namespace cleanly defines the input types for objects and arrays, including support for heterogeneous arrays via constructor functions.


69-111: LGTM!

The FieldSchema namespace provides a comprehensive type hierarchy for field schemas with proper value extraction through ToValue and its helpers. The recursive type mappings correctly handle nested object and array structures.


113-179: LGTM!

The NormalizedField namespace correctly maps from FieldSchema to normalized field representations. The FromFieldSchema conditional type comprehensively handles all schema variants including the edge case of basic fields with explicit validate property.


193-194: Typo has been fixed.

The type is now correctly named ObjectFromObjectInput (previously flagged as ObjecFromObjectInput in a past review).


181-260: LGTM!

The Field namespace is well-structured with proper type mappings. BaseFieldProperties correctly defines the common interface for all field types, and the specialized Basic, Object, and Array types extend it appropriately with their specific properties.


262-312: LGTM!

The State namespace provides a clean discriminated union for valid/invalid states via the Common & (Valid | Invalid) pattern. The Readonly, ReadonlyWithHistory, and ReadWrite interfaces properly model the state access patterns with appropriate subscription types.


314-354: LGTM!

The Snapshot namespace correctly models the snapshot structure with proper value and error extraction. The FromField type mapping and MapToValues/MapToErrors helpers provide clean recursive type transformations.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/snapshot.type.test.ts (1)

131-133: Incomplete test block—missing type assertions.

This block extracts the snapshot result but doesn't verify its type with expectNotAny, expectNotNever, or expectAssignable. Consider adding assertions for consistency with other test blocks.

🔎 Suggested fix
 {
   const result = snapshot.get(form)
+  expectNotAny(result)
+  expectNotNever(result)
+  // Add expectAssignable with the expected full form snapshot shape
 }
src/hooks.js (1)

12-23: Consider stronger typing for onSubmit callback.

The onSubmit parameter is typed as (snapshot: any) => void. Since the form structure is known via generic A, the snapshot type could be derived for better type safety.

🔎 Potential improvement
  * @arg {{
  *   fields: A,
  *   initialValues?: Expand<InitialValue<A>>,
  *   validate?: C,
- *   onSubmit: (snapshot: any) => void,
+ *   onSubmit: (snapshot: Expand<Snapshot.FromField<Field.ObjectFromObjectInput<A>>>) => void,
  *   formId?: string,
  * }} props

This would propagate type information to consumers of the form snapshot.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 826d2a8 and fb478f7.

📒 Files selected for processing (4)
  • src/components.type.test.ts
  • src/hooks.js
  • src/hooks.type.test.ts
  • src/snapshot.type.test.ts
🧰 Additional context used
🧬 Code graph analysis (4)
src/snapshot.type.test.ts (6)
src/hooks.js (2)
  • useForm (24-55)
  • form (30-34)
src/validation.js (2)
  • optionalT (49-51)
  • requiredT (59-61)
src/schema.js (2)
  • object (19-21)
  • array (39-41)
src/type-helpers.js (1)
  • asConst (5-7)
src/type.test.helpers.ts (3)
  • expectNotNever (2-2)
  • expectAssignable (15-15)
  • Prepared (28-29)
src/types.ts (2)
  • Falsy (24-24)
  • ValidationError (46-46)
src/hooks.js (3)
src/type-helpers.js (1)
  • asAny (10-12)
src/components.js (1)
  • useFormFieldSnapshot (36-36)
src/snapshot.js (3)
  • field (49-49)
  • field (75-75)
  • field (100-100)
src/hooks.type.test.ts (5)
src/hooks.js (9)
  • useForm (24-55)
  • useFormFieldValue (128-130)
  • useFormFieldsValues (136-138)
  • useFormField (144-150)
  • useNumberFormField (155-167)
  • useBooleanFormField (170-180)
  • useObjectFormField (197-202)
  • useArrayFormField (186-191)
  • useFormFieldSnapshot (112-121)
src/validation.js (1)
  • optionalT (49-51)
src/schema.js (2)
  • object (19-21)
  • array (39-41)
src/type.test.helpers.ts (1)
  • expectNotAny (12-12)
src/types.ts (8)
  • Field (181-181)
  • Basic (163-166)
  • Basic (222-232)
  • Basic (283-285)
  • Basic (326-327)
  • State (262-262)
  • Event (234-238)
  • Snapshot (314-314)
src/components.type.test.ts (5)
src/hooks.js (2)
  • useForm (24-55)
  • form (30-34)
src/validation.js (1)
  • optionalT (49-51)
src/schema.js (2)
  • object (19-21)
  • array (39-41)
src/components.js (4)
  • FormFieldValue (13-16)
  • value (14-14)
  • FormFieldsValues (24-27)
  • FormFieldValid (35-38)
src/type.test.helpers.ts (4)
  • expectNotAny (12-12)
  • expectNotNever (2-2)
  • expectAssignable (15-15)
  • Prepared (28-29)
🪛 Biome (2.1.2)
src/snapshot.type.test.ts

[error] 9-9: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

src/hooks.js

[error] 10-10: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 24-24: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

src/hooks.type.test.ts

[error] 17-17: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 57-57: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 60-60: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 63-63: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 77-77: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 91-91: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 105-105: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 119-119: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 144-144: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

src/components.type.test.ts

[error] 7-7: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🔇 Additional comments (8)
src/snapshot.type.test.ts (1)

1-32: Type test setup looks good.

The form definition with diverse field types (primitives, nested object, array, and dynamic array with discriminant) provides thorough coverage of the snapshot typing system. The Biome warning about hooks being called conditionally is a false positive—this is a type test file, not a React component.

src/components.type.test.ts (1)

1-74: Well-structured component type tests.

The tests thoroughly validate the typing for FormFieldValue, FormFieldsValues, and FormFieldValid render prop components. Type assertions correctly match field definitions. The Biome warning is a false positive—this is a type test file, not a React component.

src/hooks.type.test.ts (2)

134-138: Verify remove helper field types include undefined.

The original fields are defined with optionalT, meaning types should be Field.Basic<string | undefined>, Field.Basic<number | undefined>, etc. The assertion here uses non-optional types which may cause type mismatches.

🔎 Potential fix
         remove(entry: Field.Object<{
-          a: Field.Basic<string>
-          b: Field.Basic<number>
-          c: Field.Basic<boolean>
+          a: Field.Basic<string | undefined>
+          b: Field.Basic<number | undefined>
+          c: Field.Basic<boolean | undefined>
         }>): void

1-164: Comprehensive hook type coverage.

The test file provides good coverage for all form-related hooks with proper type assertions. The Biome warnings about conditional hook calls are false positives since this is a type test file, not a React component.

src/hooks.js (4)

24-55: useForm implementation with enhanced typing looks correct.

The previous typo (ObjecFromObjectInput) has been fixed. The lazy initialization pattern with React.useRef and equality check for re-initialization is appropriate. The Biome warning about conditional hook calls is a false positive—useFormId() in the default parameter is a valid pattern and evaluates unconditionally when no formId is provided.


62-74: Lazy state initialization pattern is correct.

Using React.useState(state.get) (function reference, not invocation) correctly leverages React's lazy initialization to defer computing the initial value until needed.


76-105: useFieldStates implementation is well-typed.

The return type cast to State.StateTupleToValueTuple<T> correctly maps the state tuple to values. The comment at lines 204-212 explains the rationale for passing arrays directly as dependencies.


107-202: Hook implementations with JSDoc templates look solid.

All field hooks (useFormFieldSnapshot, useFormFieldValue, useFormFieldsValues, useFormField, useNumberFormField, useBooleanFormField, useArrayFormField, useObjectFormField) are properly annotated with templates and return types. The @satisfies annotation on line 114 ensures the memoized state object conforms to State.Readonly.

Comment on lines +57 to +58
const formFieldValue = useFormFieldValue(form.fields.a)
expectAssignable<string, Prepared<typeof formFieldValue>>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/hooks.type.test.ts | head -100

Repository: kaliberjs/forms

Length of output: 3306


🏁 Script executed:

rg -A 15 'function useFormFieldValue' src/

Repository: kaliberjs/forms

Length of output: 606


🏁 Script executed:

rg -A 10 'useFormFieldValue' src/hooks.ts src/hooks.d.ts 2>/dev/null || rg -A 10 'export.*useFormFieldValue' src/

Repository: kaliberjs/forms

Length of output: 486


🏁 Script executed:

fd -e "d.ts" -e "ts" src/hooks

Repository: kaliberjs/forms

Length of output: 41


🏁 Script executed:

cat -n src/types.ts | head -150

Repository: kaliberjs/forms

Length of output: 6094


🏁 Script executed:

cat -n src/types.ts | sed -n '150,300p'

Repository: kaliberjs/forms

Length of output: 5291


🏁 Script executed:

rg -A 10 'function useFormFieldState' src/

Repository: kaliberjs/forms

Length of output: 450


🏁 Script executed:

cat -n src/validation.ts | grep -A 5 "optionalT"

Repository: kaliberjs/forms

Length of output: 108


🏁 Script executed:

fd -e "ts" -e "js" | xargs grep -l "optionalT"

Repository: kaliberjs/forms

Length of output: 194


🏁 Script executed:

cat -n src/validation.js | grep -B 5 -A 10 "optionalT"

Repository: kaliberjs/forms

Length of output: 512


🏁 Script executed:

rg -B 5 -A 15 "type Prepared" src/type.test.helpers.ts

Repository: kaliberjs/forms

Length of output: 742


🏁 Script executed:

cat -n src/hooks.js | sed -n '1,100p' | grep -B 10 -A 5 "useFormFieldValue"

Repository: kaliberjs/forms

Length of output: 41


🏁 Script executed:

rg -B 3 -A 8 "useFormFieldValue" src/hooks.js

Repository: kaliberjs/forms

Length of output: 338


Type assertion is incorrect—expected type should include undefined.

Field a is defined as optionalT('string'), which yields Field.Basic<string | undefined>. The assertion at line 58 expects only string, but useFormFieldValue returns the field's value state, which has type string | undefined. This is inconsistent with the similar test at line 60-61, which correctly includes undefined for optional fields. Change expectAssignable<string, ...> to expectAssignable<string | undefined, ...>.

🧰 Tools
🪛 Biome (2.1.2)

[error] 57-57: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🤖 Prompt for AI Agents
In src/hooks.type.test.ts around lines 57 to 58, the type assertion expects a
plain string but field `a` is optional (optionalT('string')) so the returned
value can be string | undefined; update the assertion to expect `string |
undefined` instead of `string` so the test matches the optional field's type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants