@jarenjs/validate 0.8.4 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +1131 -0
- package/LICENSE +21 -0
- package/README.md +796 -2
- package/dist/types/array.d.ts +2 -0
- package/dist/types/bigint.d.ts +1 -0
- package/dist/types/combine.d.ts +1 -0
- package/dist/types/condition.d.ts +1 -0
- package/dist/types/content.d.ts +3 -0
- package/dist/types/data.d.ts +7 -0
- package/dist/types/dollar-data.d.ts +11 -0
- package/dist/types/dynamic-ref.d.ts +44 -0
- package/dist/types/enum.d.ts +1 -0
- package/dist/types/format.d.ts +21 -0
- package/dist/types/index.d.ts +972 -0
- package/dist/types/messages.d.ts +142 -0
- package/dist/types/normalize.d.ts +107 -0
- package/dist/types/number.d.ts +1 -0
- package/dist/types/object.d.ts +3 -0
- package/dist/types/query-keyword.d.ts +19 -0
- package/dist/types/query.d.ts +29 -0
- package/dist/types/schema.d.ts +1 -0
- package/dist/types/string.d.ts +1 -0
- package/dist/types/tools.d.ts +109 -0
- package/dist/types/traverse.d.ts +32 -0
- package/dist/types/unevaluated.d.ts +12 -0
- package/docs/ERROR-MESSAGES.md +251 -0
- package/package.json +37 -7
- package/src/array.js +610 -0
- package/src/bigint.js +108 -0
- package/src/combine.js +276 -0
- package/src/condition.js +129 -0
- package/src/content.js +83 -0
- package/src/data.js +101 -0
- package/src/dollar-data.js +212 -0
- package/src/dynamic-ref.js +121 -0
- package/src/enum.js +147 -0
- package/src/format.js +108 -0
- package/src/index.js +1896 -0
- package/src/messages.js +497 -0
- package/src/normalize.js +585 -0
- package/src/number.js +169 -0
- package/src/object.js +848 -0
- package/src/query-keyword.js +99 -0
- package/src/query.js +85 -0
- package/src/schema.js +690 -0
- package/src/string.js +164 -0
- package/src/tools.js +397 -0
- package/src/traverse.js +442 -0
- package/src/unevaluated.js +173 -0
- package/dist/index.js +0 -1998
- package/dist/index.js.map +0 -7
- package/dist/index.min.js +0 -2
- package/dist/index.min.js.map +0 -7
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Jaren Error Messages & i18n
|
|
2
|
+
|
|
3
|
+
**Status: normative.** This document specifies the error record shape, the
|
|
4
|
+
message-key space, the `MessageSpec` value type, the `errorMessage`
|
|
5
|
+
keyword, catalogs, and the resolution precedence chain implemented by
|
|
6
|
+
`@jarenjs/validate` (src/messages.js) and mirrored by `@jarenjs/forms`
|
|
7
|
+
(src/messages.js). Locale packs live in `@jarenjs/locales`.
|
|
8
|
+
|
|
9
|
+
The design premise: **the validator and the forms engine are not the place
|
|
10
|
+
where prose is born.** Every failure is identified by a stable message key
|
|
11
|
+
(`msgid`) plus raw structured `params`; human text is produced only at
|
|
12
|
+
report/render time, over the already-failed set, by a locale catalog. The
|
|
13
|
+
validation hot path (boolean mode, the `skipErrors` no-op handlers, the
|
|
14
|
+
fast-path node compilers) is untouched: switching locale never recompiles
|
|
15
|
+
a validator or a form model's rules.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. The error record
|
|
20
|
+
|
|
21
|
+
Collect-mode validation (`collectErrors: true`) returns
|
|
22
|
+
`{ valid, errors: ValidationError[] }` where every error is:
|
|
23
|
+
|
|
24
|
+
| member | type | meaning |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `keyword` | string | the failed keyword (`type`, `required`, `$query`, ...) |
|
|
27
|
+
| `instancePath` | string | RFC 6901 JSON Pointer to the failing data location |
|
|
28
|
+
| `schemaPath` | string | the schema location (the compiled node's URI path) |
|
|
29
|
+
| `params` | object | keyword-specific raw parameters (section 2) |
|
|
30
|
+
| `msgid` | string | the resolved message key (section 2) |
|
|
31
|
+
| `message` | string | human text — `''` when `messages: false` |
|
|
32
|
+
|
|
33
|
+
`toJSON()` serializes exactly these six members.
|
|
34
|
+
|
|
35
|
+
`msgid` resolution: the matched `errorMessage` spec's `$msgid` if any,
|
|
36
|
+
else `params.code` if present (the `$query` runtime codes), else the
|
|
37
|
+
keyword.
|
|
38
|
+
|
|
39
|
+
With the validator option `messages: false`, conversion skips message
|
|
40
|
+
rendering entirely (`message: ''`; `params` and `msgid` still set) — the
|
|
41
|
+
fast path for applications that render exclusively through
|
|
42
|
+
`localizeErrors` or their own resolver.
|
|
43
|
+
|
|
44
|
+
### The handler-contract invariant (internal)
|
|
45
|
+
|
|
46
|
+
Every internal error handler call site obeys: normal handlers
|
|
47
|
+
`addError(data, dataPath, ...extra)`, keyed handlers
|
|
48
|
+
`addKeyedError(dataKey, data, dataPath, ...extra)` — the first meta
|
|
49
|
+
argument is ALWAYS the instance data path, so `instancePath` is a straight
|
|
50
|
+
read. A charCode guard keeps a non-pointer value from ever becoming a
|
|
51
|
+
wrong path (it yields `''`).
|
|
52
|
+
|
|
53
|
+
### Deliberate `additionalProperties` divergence from ajv
|
|
54
|
+
|
|
55
|
+
An `additionalProperties: false` failure reports `instancePath` at the
|
|
56
|
+
**offending member** (`/nested/extra`), not at the parent object
|
|
57
|
+
(`/nested`). The object compiler passes the child path
|
|
58
|
+
(`${dataPath}/${dataKey}`) as the error's data path, so the pointer lands
|
|
59
|
+
on the disallowed property itself. This is spec-correct (the failing
|
|
60
|
+
location *is* that property) and friendlier for a UI that highlights the
|
|
61
|
+
field. ajv reports the parent object's path with the property name in
|
|
62
|
+
`params.additionalProperty`; Jaren carries the same `additionalProperty`
|
|
63
|
+
param, so a consumer diffing error sets against ajv should treat the
|
|
64
|
+
`instancePath` difference as intentional, not a bug.
|
|
65
|
+
|
|
66
|
+
## 2. The message-key registry
|
|
67
|
+
|
|
68
|
+
One flat namespace:
|
|
69
|
+
|
|
70
|
+
| Producer | Keys | Params carried |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| validate keywords | the keyword itself: `type`, `required`, `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf`, `minLength`, `maxLength`, `pattern`, `additionalProperties`, `minProperties`, `maxProperties`, `minItems`, `maxItems`, `uniqueItems`, `contains`, `items`, `allOf`, `anyOf`, `oneOf`, `not`, `format`, `if`, `then`, `else`, `false schema`, `$query` | `type`/`types`; `missingProperty`; `limit` + `comparison`; `multipleOf`; `pattern`; `additionalProperty`; `format` |
|
|
73
|
+
| query runtime via `$query` | the `JQ2xxx` code (`JQ2001`, `JQ2003`, ...) | `{ code, docPath }` |
|
|
74
|
+
| forms field checks | `form/` + the emitted keyword: `form/required`, `form/type`, `form/const`, `form/enum`, `form/minLength`, `form/maxLength`, `form/pattern`, `form/format`, `form/minimum`, `form/maximum`, `form/exclusiveMinimum`, `form/exclusiveMaximum`, `form/multipleOf`, `form/minItems`, `form/maxItems`, `form/uniqueItems`, `form/minProperties`, `form/maxProperties` | per branch: `limit`, `len`, `type`, `format`, `enumValues`, `constValue`, `pattern`, `multipleOf` |
|
|
75
|
+
| forms rules | `x-form/assert` (the default), else the author's `$msgid` | `{ pointer, ...spec.params }` |
|
|
76
|
+
| applications | any `$msgid` they invent — recommend dotted names (`checkout.total-too-low`) | error params + spec params |
|
|
77
|
+
|
|
78
|
+
Keywords without a built-in English entry (`const`, `enum`,
|
|
79
|
+
`dependentRequired`, `dependencies`, `unevaluatedProperties`, ...) render
|
|
80
|
+
the generic `validation failed for keyword '<keyword>'`; their `msgid` is
|
|
81
|
+
still the keyword, so a catalog (or an application) may cover them.
|
|
82
|
+
|
|
83
|
+
## 3. MessageSpec
|
|
84
|
+
|
|
85
|
+
The value type used identically by `errorMessage` (validate),
|
|
86
|
+
`x-form.message` (forms), and anywhere else a message is authored:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
MessageSpec = string // inline template (author's language)
|
|
90
|
+
| { "$msgid": string, // catalog key to resolve at render time
|
|
91
|
+
"message"?: string, // inline template fallback on catalog miss
|
|
92
|
+
"params"?: object } // merged OVER the error's params
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The object form requires `$msgid` and/or `message`; any other member is a
|
|
96
|
+
compile-time error. Spec `params` merge over the error's params **into the
|
|
97
|
+
error record**, so post-hoc localization sees them too.
|
|
98
|
+
|
|
99
|
+
### Template syntax
|
|
100
|
+
|
|
101
|
+
Applies to inline templates and to string-valued catalog entries:
|
|
102
|
+
|
|
103
|
+
- `{name}` substitutes the merged params member `name` — `String(v)` for
|
|
104
|
+
primitives, `JSON.stringify(v)` otherwise;
|
|
105
|
+
- an unknown name leaves the placeholder literally (debuggability);
|
|
106
|
+
- `{{` escapes a literal `{`.
|
|
107
|
+
|
|
108
|
+
Templates compile ONCE into a closure (`compileMessageTemplate`) — the
|
|
109
|
+
two-stage house rule applies to messages too. There is **no pointer/data
|
|
110
|
+
interpolation in v1** (`${/foo}` ajv-style is a roadmap follow-up; params
|
|
111
|
+
already carry the relevant values).
|
|
112
|
+
|
|
113
|
+
## 4. Catalogs
|
|
114
|
+
|
|
115
|
+
A catalog is a plain flat object:
|
|
116
|
+
`{ [key]: (params, error) => string | templateString }`.
|
|
117
|
+
`compileMessageCatalog(catalogLike)` returns a functions-only frozen copy
|
|
118
|
+
(template strings compiled).
|
|
119
|
+
|
|
120
|
+
English catalogs are **built in**: validate's `messagesEn`
|
|
121
|
+
(src/messages.js) and forms' `formsMessagesEn` (src/messages.js) — neither
|
|
122
|
+
package gains a dependency. Non-English packs live in `@jarenjs/locales`
|
|
123
|
+
(`nl`, `fr`, `es`, `pt`, `de`, `ja`, `ko`, `zhTW`, `ru`, `tr`, `ar` —
|
|
124
|
+
each also a subpath export, e.g. `@jarenjs/locales/fr`,
|
|
125
|
+
`@jarenjs/locales/zh-tw`) and must have key parity with the built-in
|
|
126
|
+
English (enforced by repo tests).
|
|
127
|
+
|
|
128
|
+
### Pack authoring (globalization mechanics)
|
|
129
|
+
|
|
130
|
+
Catalog entries are functions precisely so packs can use the platform:
|
|
131
|
+
|
|
132
|
+
- `Intl.PluralRules` for plural category selection ("1 teken" /
|
|
133
|
+
"2 tekens" — see the `nl` pack's `minLength`),
|
|
134
|
+
- `Intl.NumberFormat` for `{limit}`-style numbers,
|
|
135
|
+
- `Intl.ListFormat` for enum lists.
|
|
136
|
+
|
|
137
|
+
Hold these as module-level singletons (allocation discipline). **Bidi:**
|
|
138
|
+
packs targeting RTL scripts should isolate interpolated user values with
|
|
139
|
+
FSI/PDI (U+2068/U+2069) — the pack author's call, not core's. A pack
|
|
140
|
+
depends on nothing outside the Jaren suite, and inside it on nothing but
|
|
141
|
+
`@jarenjs/core` — never on `@jarenjs/validate` or `@jarenjs/forms`, so
|
|
142
|
+
either consumer can serve any pack.
|
|
143
|
+
|
|
144
|
+
## 5. Resolution precedence
|
|
145
|
+
|
|
146
|
+
Implemented in validate's conversion (`convertInternalErrors`); forms
|
|
147
|
+
mirrors the tail of the chain:
|
|
148
|
+
|
|
149
|
+
1. the nearest `errorMessage` spec for the error (section 6 matching);
|
|
150
|
+
2. if the spec has `$msgid`: active catalog → built-in English catalog →
|
|
151
|
+
the spec's inline `message` template;
|
|
152
|
+
3. if the spec is inline (string / `message`-only): render it;
|
|
153
|
+
4. no spec: active catalog[`msgid`] → built-in English[`msgid`] →
|
|
154
|
+
catalog[`keyword`] → English[`keyword`] (so uncovered `JQ*` codes still
|
|
155
|
+
say something) → `validation failed for keyword '<keyword>'`.
|
|
156
|
+
|
|
157
|
+
At conversion time the "active catalog" is the built-in English; other
|
|
158
|
+
locales enter through `localizeErrors` / `renderErrorMessage` (section 7)
|
|
159
|
+
or, in forms, through the `catalog` parameters.
|
|
160
|
+
|
|
161
|
+
## 6. The `errorMessage` keyword
|
|
162
|
+
|
|
163
|
+
**Overrides text, never structure.** Deliberate divergences from
|
|
164
|
+
ajv-errors:
|
|
165
|
+
|
|
166
|
+
- errors are never removed, merged, or aggregated;
|
|
167
|
+
- no synthetic `keyword: "errorMessage"` error is created;
|
|
168
|
+
- originals are never moved into `params.errors`;
|
|
169
|
+
- no `${/pointer}` data interpolation (v1);
|
|
170
|
+
- no `properties`/`items` map forms — the subtree prefix rule covers what
|
|
171
|
+
those express (roadmap follow-up if demand appears).
|
|
172
|
+
|
|
173
|
+
An `errorMessage` spec only changes what `message` (and `msgid`) say on
|
|
174
|
+
the errors it matches.
|
|
175
|
+
|
|
176
|
+
### Grammar
|
|
177
|
+
|
|
178
|
+
Validated at schema compile time; malformed specs throw with the schema
|
|
179
|
+
path.
|
|
180
|
+
|
|
181
|
+
```jsonc
|
|
182
|
+
"errorMessage": MessageSpec // string form: whole subtree
|
|
183
|
+
"errorMessage": { // map form
|
|
184
|
+
"minLength": MessageSpec, // per failing keyword, this node
|
|
185
|
+
"required": MessageSpec // all required failures here
|
|
186
|
+
| { "vatId": MessageSpec, ... }, // or per missing property
|
|
187
|
+
"$query": MessageSpec // EBV-false and any runtime code
|
|
188
|
+
| { "default": MessageSpec, // EBV-false
|
|
189
|
+
"JQ2001": MessageSpec, ... }, // per runtime code
|
|
190
|
+
"_": MessageSpec // node-level catch-all
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Matching
|
|
195
|
+
|
|
196
|
+
The compiler registers each spec by schema location on the compilation
|
|
197
|
+
root — **no validator closure is emitted**; the keyword contributes zero
|
|
198
|
+
validation-time work and never knocks a node off the fast paths. At
|
|
199
|
+
report time:
|
|
200
|
+
|
|
201
|
+
- candidate nodes are the registered locations that equal the error's
|
|
202
|
+
`schemaPath` or are a segment-aware prefix of it (`/foo` never matches
|
|
203
|
+
`/foobar`); the longest prefix is tried first;
|
|
204
|
+
- **at the error's own node**: keyword-map entry (with `required`
|
|
205
|
+
per-property matching on `params.missingProperty`, `$query` per-code on
|
|
206
|
+
`params.code`, `default` on its absence) > `_` > string form;
|
|
207
|
+
- **at an ancestor**: only the string form applies (it covers the
|
|
208
|
+
subtree — this is what lets one string on a `oneOf` replace the branch
|
|
209
|
+
noise);
|
|
210
|
+
- no match at the nearest node falls through to farther ancestors.
|
|
211
|
+
|
|
212
|
+
## 7. Post-hoc localization
|
|
213
|
+
|
|
214
|
+
```js
|
|
215
|
+
import { JarenValidator, compileMessageCatalog, localizeErrors } from '@jarenjs/validate';
|
|
216
|
+
import { nl } from '@jarenjs/locales';
|
|
217
|
+
|
|
218
|
+
const catalog = compileMessageCatalog(nl);
|
|
219
|
+
const result = validate(data); // English messages
|
|
220
|
+
localizeErrors(result.errors, catalog); // Dutch messages, same array
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`localizeErrors(errors, catalog)` re-renders `message` on each error from
|
|
224
|
+
`msgid` + `params` through the given compiled catalog with built-in
|
|
225
|
+
English fallback. Contract details:
|
|
226
|
+
|
|
227
|
+
- **inline schema-authored text without `$msgid` is single-language by
|
|
228
|
+
definition and is NOT re-rendered** — that is why `$msgid` exists;
|
|
229
|
+
- an error whose `msgid` resolves in no catalog keeps its current message
|
|
230
|
+
(e.g. a custom `$msgid`'s inline fallback text);
|
|
231
|
+
- `renderErrorMessage(error, catalog?)` is the single-error form of the
|
|
232
|
+
same chain (section 5 step 4).
|
|
233
|
+
|
|
234
|
+
Forms renders eagerly (failure-only, cheap, keeps UI consumers simple)
|
|
235
|
+
but through the same catalog contract: `validateField` /
|
|
236
|
+
`validateAllFields` / `evaluateFormRules` accept an optional compiled
|
|
237
|
+
catalog, default English, and every `FieldError` carries
|
|
238
|
+
`{ keyword, params, msgid, message }` so consumers can re-render.
|
|
239
|
+
|
|
240
|
+
## 8. Why not MessageFormat 2
|
|
241
|
+
|
|
242
|
+
`Intl.MessageFormat` (MessageFormat 2) is TC39 Stage 2 and stalled,
|
|
243
|
+
shipping in no runtime; adopting it would mean a runtime dependency or a
|
|
244
|
+
homegrown MF2 engine for pluralization the platform already provides
|
|
245
|
+
through `Intl.PluralRules`. Catalogs-as-functions cover the same ground
|
|
246
|
+
with no runtime dependency beyond the platform, and with full generality.
|
|
247
|
+
|
|
248
|
+
**Revisit trigger:** Intl.MessageFormat reaching TC39 Stage 3 or shipping
|
|
249
|
+
in a major runtime. At that point, MF2 syntax could become a supported
|
|
250
|
+
catalog *entry format* (compiled by `compileMessageCatalog`) without
|
|
251
|
+
changing the catalog contract.
|
package/package.json
CHANGED
|
@@ -1,20 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jarenjs/validate",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.34.0",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./dist/types/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/types/index.d.ts",
|
|
12
|
+
"default": "./src/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./query": {
|
|
15
|
+
"types": "./dist/types/query.d.ts",
|
|
16
|
+
"default": "./src/query.js"
|
|
17
|
+
},
|
|
18
|
+
"./normalize": {
|
|
19
|
+
"types": "./dist/types/normalize.d.ts",
|
|
20
|
+
"default": "./src/normalize.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
7
24
|
"files": [
|
|
8
|
-
"dist"
|
|
25
|
+
"dist/types/",
|
|
26
|
+
"src/",
|
|
27
|
+
"docs/",
|
|
28
|
+
"ARCHITECTURE.md"
|
|
9
29
|
],
|
|
10
30
|
"description": "Jaren is a JavaScript JSON Schema Validator",
|
|
11
31
|
"author": "joham",
|
|
12
32
|
"repository": {
|
|
13
33
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/jklarenbeek/jarenjs.git",
|
|
34
|
+
"url": "git+https://github.com/jklarenbeek/jarenjs.git",
|
|
15
35
|
"directory": "packages/validate"
|
|
16
36
|
},
|
|
17
37
|
"license": "MIT",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=24"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"registry": "https://registry.npmjs.org/"
|
|
44
|
+
},
|
|
18
45
|
"keywords": [
|
|
19
46
|
"jaren",
|
|
20
47
|
"json",
|
|
@@ -23,9 +50,12 @@
|
|
|
23
50
|
"validator"
|
|
24
51
|
],
|
|
25
52
|
"scripts": {
|
|
26
|
-
"build": "node esbuild.config.js"
|
|
53
|
+
"build": "node esbuild.config.js && npm run build:types",
|
|
54
|
+
"build:types": "tsc -p tsconfig.json",
|
|
55
|
+
"prepack": "npm run build:types"
|
|
27
56
|
},
|
|
28
|
-
"
|
|
29
|
-
"
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@jarenjs/core": "^0.34.0",
|
|
59
|
+
"@jarenjs/json": "^0.34.0"
|
|
30
60
|
}
|
|
31
61
|
}
|