first commit
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# `registry.schemas`
|
||||
|
||||
## Purpose
|
||||
|
||||
Strict-object Zod schemas for every plugin kind accepted by `ReactI18nRegistry`. The schemas are the boundary at which a malformed plugin shape is rejected at registration time — before any callsite has a chance to consume the bad plugin and throw a less-actionable runtime error elsewhere. Each schema is `.strict()`-locked so extra fields on a plugin (typos, stale fields from an older registry version) trigger validation failure rather than silently passing through.
|
||||
|
||||
## Features
|
||||
|
||||
- One schema per plugin kind — `DetectorPluginSchema`, `FormatterPluginSchema`, `PostProcessorPluginSchema`, `LocaleAdapterPluginSchema`.
|
||||
- Non-empty id enforced (`z.string().min(1)`) so every plugin has a stable lookup key.
|
||||
- Function-shape fields validated structurally; their semantic correctness is exercised by the tests of each consumer (e.g. `useFormatter` test calls the registered plugin's `format` and asserts the output).
|
||||
- Schemas surface failure as a Zod `ZodError`, which the registry wraps in a `ReactI18nError(REACT_I18N.PLUGIN_INVALID)` with the issues attached to `cause`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { FormatterPluginSchema } from "@suaveplan/react-i18n/registry";
|
||||
|
||||
const result = FormatterPluginSchema.safeParse({
|
||||
id: "duration",
|
||||
format: (locale, args) => String(args.ms),
|
||||
});
|
||||
|
||||
result.success; // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
The schemas accept generic `format` / `process` / `resolve` / `detect` functions because their signatures cannot be expressed as a closed Zod shape. The schema verifies the function is *present* and is a function; the actual `(locale, args) => result` contract is enforced at the type-system level via `FormatterPlugin<TArgs, TResult>` and at the test level by exercising the plugin's behavior.
|
||||
|
||||
```ts
|
||||
import { DetectorPluginSchema } from "@suaveplan/react-i18n/registry";
|
||||
|
||||
const malformed = {
|
||||
id: "ab",
|
||||
priority: 0,
|
||||
detect: { name: "ab" /* missing environment */ },
|
||||
};
|
||||
|
||||
const result = DetectorPluginSchema.safeParse(malformed);
|
||||
result.success; // false
|
||||
result.error.issues; // [{ path: ["detect", "environment"], message: "Required" }]
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `DetectorPluginSchema`
|
||||
|
||||
Validates `{ id: string; priority: number; detect: { name; environment; detect?; detectAsync?; cache? } }`. The inner `detect` object is `passthrough` because `LocaleDetector` from `@suaveplan/i18n-detector` may carry forward-compatible fields the registry does not own.
|
||||
|
||||
### `FormatterPluginSchema`
|
||||
|
||||
Validates `{ id: string; schema?: unknown; defaultOptions?: unknown; format: function }`. The `schema` field is opaque (`z.unknown()`) because it is a Zod schema reference and Zod cannot meaningfully validate "is this a Zod schema."
|
||||
|
||||
### `PostProcessorPluginSchema`
|
||||
|
||||
Validates `{ id: string; priority: number; process: function }`.
|
||||
|
||||
### `LocaleAdapterPluginSchema`
|
||||
|
||||
Validates `{ id: string; resolve: function }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- All schemas are `.strict()` — extra keys cause validation failure. This catches stale fields from older registry versions and typos in plugin objects.
|
||||
- The schemas are exported from a single file rather than co-located with their plugin types so a consumer importing `@suaveplan/react-i18n/registry` for runtime validation does not pay the bundle cost of the plugin function code.
|
||||
- Validation errors are passed through to the registry which decorates them with stable error codes; consumers should never `safeParse` directly against these schemas — register through `ReactI18nRegistry.register*` to get the decorated error.
|
||||
- The schemas live in this package rather than in `@suaveplan/types` because they validate React-specific plugin shapes; the `ReactI18nRegistry` is itself React-bound (consumed via context).
|
||||
Reference in New Issue
Block a user