@uniweb/api 1.2.1 → 1.2.2
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/README.md +7 -8
- package/package.json +1 -1
- package/src/mock/definition.js +3 -1
- package/src/mock/schema-shape.js +16 -4
- package/src/mock/store.js +3 -3
- package/src/wire.js +7 -3
package/README.md
CHANGED
|
@@ -360,21 +360,20 @@ keeps it.
|
|
|
360
360
|
| `min`, `max` on `int`, `decimal` | refuses a value outside them |
|
|
361
361
|
| `format` — `email`, `url` on `string` and `text`; `currency` on `string`; `markdown`, `html` on `text` | checks `email`, `url` and `currency` (an ISO 4217 code); `markdown` and `html` mark what the text is |
|
|
362
362
|
| `localized: true` on text | holds one string for every language, or one per language |
|
|
363
|
+
| `format: year-month` or `format: year` on `date` | holds a month, `2024-05`, or a year, `2024` — ISO 8601's reduced precision, stored as written (a backend from before 2026-10-14 refuses the declaration). With no `format`, a `date` holds a day, `2024-05-14` |
|
|
363
364
|
| `enum: [...]` | keeps the field to those values — in the definition, as its section's `one_of` rule |
|
|
364
365
|
| `type: item_ref`, `options: '@acme/lists/countries'` | keeps the value to an item of that section — an item of another is `409`, `kind: 'rule'` |
|
|
365
366
|
|
|
366
367
|
A refused value is `kind: 'invalid'`, its `extensions` naming the field (`data.title`,
|
|
367
368
|
`data.label.fr` for one language), the `rule` it broke and the bound
|
|
368
|
-
(`{ rule: 'max_length', max_length: 250 }`
|
|
369
|
-
names its field
|
|
369
|
+
(`{ rule: 'max_length', max_length: 250 }`, `{ rule: 'format', format: 'year-month' }`). An
|
|
370
|
+
`enum`'s refusal, `rule: 'one_of'`, names its field too — a backend from before 2026-10-14
|
|
371
|
+
names it only in `detail`.
|
|
370
372
|
|
|
371
373
|
⛔ **A declaration the backend does not take refuses the whole schema** when it reaches
|
|
372
|
-
the backend, naming the field and the key: a `format` its kind does not have
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
⚠️ **A `date` is a calendar day**, `YYYY-MM-DD`: `2024-05` is refused. A year alone is an
|
|
377
|
-
`int`, with `min` and `max`.
|
|
374
|
+
the backend, naming the field and the key: a `format` its kind does not have (`month-day`
|
|
375
|
+
on a `date`, `year` on an `int`), a key it does not know (`help`), or one it does not check
|
|
376
|
+
yet (`pattern`, `min_length`).
|
|
378
377
|
|
|
379
378
|
### On a section — rules across fields, and across items
|
|
380
379
|
|
package/package.json
CHANGED
package/src/mock/definition.js
CHANGED
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
* | `string` · `text` | `max_length`, `languages`, `format` |
|
|
16
16
|
* | `int` | `min`, `max` |
|
|
17
17
|
* | `decimal` | `precision`, `scale`, `min`, `max` |
|
|
18
|
+
* | `date` | `format` — `year-month` or `year` |
|
|
18
19
|
* | `item_ref` | `options` (a schema's) → `section_uuid`; `entity_uuid` |
|
|
19
20
|
* | `entity_ref` | `model` (a schema's) → `model_uuid`; `model_uuids`, `contains`, `readable_with` |
|
|
20
21
|
* | `file` | `mime_types`, `max_size` |
|
|
21
22
|
* | `json` | `languages`, `format` |
|
|
22
|
-
* | `bool` · `
|
|
23
|
+
* | `bool` · `datetime` · `secret` | none |
|
|
23
24
|
*
|
|
24
25
|
* And what moves: an `enum` becomes its section's `one_of` rule; `multiple: true` is
|
|
25
26
|
* `multi`; a nested section is a section of its own.
|
|
@@ -31,6 +32,7 @@ const KIND_KEYS = {
|
|
|
31
32
|
text: ['max_length', 'languages', 'format'],
|
|
32
33
|
int: ['min', 'max'],
|
|
33
34
|
decimal: ['precision', 'scale', 'min', 'max'],
|
|
35
|
+
date: ['format'],
|
|
34
36
|
item_ref: ['entity_uuid'],
|
|
35
37
|
entity_ref: ['model_uuids', 'contains', 'readable_with'],
|
|
36
38
|
file: ['mime_types', 'max_size'],
|
package/src/mock/schema-shape.js
CHANGED
|
@@ -245,7 +245,18 @@ const TEXT_TYPES = new Set(['string', 'text', 'html', 'markdown', 'email', 'url'
|
|
|
245
245
|
/** Characters, as the backend counts them — code points, not UTF-16 units. */
|
|
246
246
|
const charCount = (s) => [...s].length
|
|
247
247
|
|
|
248
|
-
/**
|
|
248
|
+
/**
|
|
249
|
+
* A `date` at the precision its field declares, as the backend checks it (MEASURED 2026-10-14):
|
|
250
|
+
* `format: year-month` holds `YYYY-MM`, a month `01` to `12`; `format: year` holds `YYYY`; and
|
|
251
|
+
* with no `format`, a day the calendar has (`2024-5-3` passes, `2024-05` and `2024-02-30` do not).
|
|
252
|
+
*/
|
|
253
|
+
function isDateAt(value, format) {
|
|
254
|
+
if (format === 'year-month') return /^\d{4}-(0[1-9]|1[0-2])$/.test(value)
|
|
255
|
+
if (format === 'year') return /^\d{4}$/.test(value)
|
|
256
|
+
return isDate(value)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** `YYYY-MM-DD`, a day the calendar has. */
|
|
249
260
|
function isDate(value) {
|
|
250
261
|
const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(value)
|
|
251
262
|
if (!m) return false
|
|
@@ -406,7 +417,7 @@ export function checkFields(fields, data, path = '') {
|
|
|
406
417
|
|
|
407
418
|
/**
|
|
408
419
|
* The bound a value of a field's type breaks, as the backend checks it — `max_length`
|
|
409
|
-
* on text, `min` and `max` on a number,
|
|
420
|
+
* on text, `min` and `max` on a number, a `date`'s precision — or null.
|
|
410
421
|
* (MEASURED 2026-10-14, each with the bound beside the rule.)
|
|
411
422
|
*/
|
|
412
423
|
function boundOf(field, value, at) {
|
|
@@ -419,8 +430,9 @@ function boundOf(field, value, at) {
|
|
|
419
430
|
return { field: at, rule: 'max', detail: `item.data.${at}=${value} above max=${field.max}`, params: { max: field.max } }
|
|
420
431
|
}
|
|
421
432
|
}
|
|
422
|
-
if (field.type === 'date' && typeof value === 'string' && !
|
|
423
|
-
|
|
433
|
+
if (field.type === 'date' && typeof value === 'string' && !isDateAt(value, field.format)) {
|
|
434
|
+
const shape = ['year-month', 'year'].includes(field.format) ? field.format : 'date'
|
|
435
|
+
return { field: at, rule: 'format', detail: `item.data.${at} is not a valid ${shape} (\`${value}\`)`, params: { format: shape } }
|
|
424
436
|
}
|
|
425
437
|
return null
|
|
426
438
|
}
|
package/src/mock/store.js
CHANGED
|
@@ -1149,12 +1149,12 @@ export class MockStore {
|
|
|
1149
1149
|
const check = checkItemWrite({ decl: model.decl, section: section.path ?? section.name, data })
|
|
1150
1150
|
if (check.outcome === OUTCOME.VIOLATES) {
|
|
1151
1151
|
const first = check.problems[0]
|
|
1152
|
-
// As the backend refuses it (MEASURED 2026-10-14): `reason`, `rule
|
|
1153
|
-
// it —
|
|
1152
|
+
// As the backend refuses it (MEASURED 2026-10-14): `reason`, the field, the `rule` and the
|
|
1153
|
+
// bound beside it — an `enum`'s `one_of` naming its field too, as it has since that day.
|
|
1154
1154
|
if (first.rule === 'undeclared-section') return refuse(400, 'Validation', first.detail, { field: first.field })
|
|
1155
1155
|
return refuse(400, 'Validation', first.detail, {
|
|
1156
1156
|
reason: 'invalid_field',
|
|
1157
|
-
|
|
1157
|
+
field: `data.${first.field}`,
|
|
1158
1158
|
rule: first.rule,
|
|
1159
1159
|
...(first.params || {}),
|
|
1160
1160
|
})
|
package/src/wire.js
CHANGED
|
@@ -666,7 +666,10 @@ export const READ = {
|
|
|
666
666
|
* **`type.data` is the field's whole declaration**: `key`, `kind`, and what the field
|
|
667
667
|
* declares — `required`, `multi`, `label`, `description`, `localized`, `format`,
|
|
668
668
|
* `max_length`, `min`, `max` among them. `type` is a wrapper kept from an older
|
|
669
|
-
* shape: its `id` is always `-1`, and its `name` is the kind.
|
|
669
|
+
* shape: its `id` is always `-1`, and its `name` is the kind. ⭐ A `date` field's
|
|
670
|
+
* `format` is its precision — `year-month` or `year`, ISO 8601's reduced precision, its
|
|
671
|
+
* values `YYYY-MM` or `YYYY` as written — and a `date` with none holds a day (since
|
|
672
|
+
* 2026-10-14).
|
|
670
673
|
* - **a field of kind `item_ref`** — a choice from a list — carries `section_uuid`, the
|
|
671
674
|
* section whose items are its choices (a schema's `options:` path becomes it), and
|
|
672
675
|
* `entity_uuid` when it pins the entry that holds them. Its choices are
|
|
@@ -776,9 +779,10 @@ export const SECTION_ITEMS = {
|
|
|
776
779
|
* | `required` | `field: 'data.title'` |
|
|
777
780
|
* | `max_length` — in characters, per language for a localized value | `field: 'data.title'` (`'data.label.en'`), `max_length` |
|
|
778
781
|
* | `min` · `max` | `field`, and `min` or `max` |
|
|
779
|
-
* | `format` — a `date`
|
|
782
|
+
* | `format` — a `date` at another precision than its field declares: a day `YYYY-MM-DD`, or with `format: year-month` a month `YYYY-MM`, with `format: year` a year `YYYY` | `field`, `format` — the precision it should be: `'date'`, `'year-month'`, `'year'` |
|
|
783
|
+
* | `format` — a `string` or `text` that is not its `format`: `email`, `url`, `currency` | `field`, `format` |
|
|
780
784
|
* | `type` | `field` |
|
|
781
|
-
* | `one_of` — a value outside a field's `enum` | ⚠️
|
|
785
|
+
* | `one_of` — a value outside a field's `enum` | `field` — since 2026-10-14. ⚠️ A backend from before names the field only in `detail` (`item.level = "x" is not one of the allowed values …`), so read `field` when it is there |
|
|
782
786
|
*/
|
|
783
787
|
export const PROBLEM = {
|
|
784
788
|
csrf: 'CSRF Header Required',
|