跪拜 Guibai
← All articles
HarmonyOS · Frontend · Backend

SchemaFactory: The Fix for Mutated Validation Rules Leaking Across ArkTS Forms

By 鸿蒙开发 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A single mutated schema object can enforce stale limits across different forms, rejecting valid input and making rules unpredictable. SchemaFactory gives teams a way to reuse rule creation logic while guaranteeing per-use instances, cutting off a whole class of cross-form validation bugs.

Summary

Mutable chain calls in @hmkit/validator make shared schema instances a trap. Calling .max(8) and then .max(32) on the same object appends a second constraint rather than replacing the first, so a 9-character string that should pass a 32-char limit still fails. Two variables can silently reference the same mutable schema, and the article demonstrates how a registration form's 8-char cap leaks into a profile page's 32-char rule.

The 1.4.0 release introduces SchemaFactory as a formal fix. It holds a base constructor plus derive callbacks; each create() re-executes the base constructor and applies the config, producing a fresh instance per rule set. This lets teams reuse rule creation logic while guaranteeing isolation, without changing the existing mutable API.

Caveats remain: partial() returns a new outer object but keeps sub-schemas shared, and SchemaFactory will happily return an external instance if a constructor hands one back. Constructors and derive callbacks may run many times, so side effects like network calls belong in business flow, not in factories.

Takeaways
— In @hmkit/validator's mutable chain API, .max() appends a rule and returns the same instance, so calling .max(8) then .max(32) on one object leaves both constraints active.
— Using a plain factory function that calls liteV.string().required().max(n) each time creates independent StringSchema instances, so constraints don't mix.
— SchemaFactory stores a base constructor and derive callbacks; each create() re-runs the base constructor and applies the stored configuration.
— partial() on an object schema returns a new schema that allows missing fields, but the inner sub-schemas are still shared references.
— SchemaFactory does not deep-copy an object returned from a constructor; if the constructor returns an external shared instance, that instance is reused across create() calls.
— SchemaFactory constructors and derive callbacks may run multiple times, so side effects like API calls or state writes should stay out of them.
Conclusions

The mutable chain API is the root cause; 'reuse the rule definition' sounds like copying, but it aliases the same object. SchemaFactory makes construction explicit instead of relying on implicit copying.

The factory approach is a compatibility compromise: it keeps the old mutable API intact for existing callers while offering an opt-in path to isolation for those who need it.

A SchemaFactory is only as safe as the constructor it wraps; capturing external mutable state in the callback reintroduces the sharing problem, so the pattern demands discipline around what gets closed over.

Concepts & terms
SchemaFactory
A class in @hmkit/validator that holds a base constructor and a list of derive callbacks. Each create() call re-executes the base constructor and applies the derived configuration, producing a fresh schema instance.
partial()
A method on object schemas that returns a new schema where specified fields may be missing. It creates a new outer object but does not deep-copy the sub-schemas, which remain shared references.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗