@asteby/metacore-runtime-react 25.1.3 → 25.1.5
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/CHANGELOG.md +22 -0
- package/dist/action-modal-dispatcher.d.ts.map +1 -1
- package/dist/action-modal-dispatcher.js +76 -13
- package/dist/dialogs/dynamic-record.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.js +58 -8
- package/dist/dialogs/normalize-submit.d.ts +17 -4
- package/dist/dialogs/normalize-submit.d.ts.map +1 -1
- package/dist/dialogs/normalize-submit.js +43 -10
- package/dist/server-error.d.ts +26 -1
- package/dist/server-error.d.ts.map +1 -1
- package/dist/server-error.js +66 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/extract-field-errors.test.ts +102 -0
- package/src/__tests__/normalize-ref-fields.test.ts +45 -0
- package/src/action-modal-dispatcher.tsx +84 -12
- package/src/dialogs/dynamic-record.tsx +65 -8
- package/src/dialogs/normalize-submit.ts +46 -17
- package/src/server-error.ts +87 -2
- package/src/types.ts +8 -0
package/src/server-error.ts
CHANGED
|
@@ -71,8 +71,93 @@ export function extractServerError(err: unknown, fallbackTitle: string): Extract
|
|
|
71
71
|
return { title: fallbackTitle }
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/** i18n translator, tolerant of non-keys (returns `defaultValue`/the input).
|
|
75
|
-
|
|
74
|
+
/** i18n translator, tolerant of non-keys (returns `defaultValue`/the input).
|
|
75
|
+
* Accepts arbitrary interpolation values (e.g. `label`, plus a code's `params`)
|
|
76
|
+
* so `localizeFieldIssue` can pass `{{label}}` and friends through i18next. */
|
|
77
|
+
export type Translate = (key: string, opts?: { defaultValue?: string; [k: string]: unknown }) => string
|
|
78
|
+
|
|
79
|
+
// ── Per-field validation errors ────────────────────────────────────────────
|
|
80
|
+
// The kernel answers a failed create/edit with HTTP 422 and a per-field map:
|
|
81
|
+
// { success:false, message:"validation failed",
|
|
82
|
+
// errors: { "<fieldKey>": [ { code, params } ] } }
|
|
83
|
+
// Codes are locale-agnostic; the SDK localizes them to Spanish using the field
|
|
84
|
+
// label. Some endpoints (7leguas-style) instead send pre-localized STRINGS —
|
|
85
|
+
// so a value entry may be an object `{code,params}` OR a plain string.
|
|
86
|
+
|
|
87
|
+
/** A single normalized validation issue for one field: either a machine `code`
|
|
88
|
+
* (+ optional `params`) to localize, or a ready-to-show `message` string. */
|
|
89
|
+
export interface FieldIssue {
|
|
90
|
+
code?: string
|
|
91
|
+
params?: Record<string, any>
|
|
92
|
+
message?: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Spanish defaults per known validation code. `{{label}}` (and code params like
|
|
96
|
+
* `allowed`/`ref`/`expected`) are interpolated by i18next, so a host can override
|
|
97
|
+
* any of these via its addon i18n bundle under `validation.<code>`. */
|
|
98
|
+
const VALIDATION_DEFAULTS: Record<string, string> = {
|
|
99
|
+
required: 'El campo {{label}} es obligatorio',
|
|
100
|
+
invalid_option: 'El valor de {{label}} no es válido',
|
|
101
|
+
not_found: 'El {{label}} seleccionado no existe',
|
|
102
|
+
duplicate: 'Ya existe un registro con ese {{label}}',
|
|
103
|
+
invalid_type: 'El campo {{label}} tiene un formato inválido',
|
|
104
|
+
}
|
|
105
|
+
const VALIDATION_FALLBACK = '{{label}}: valor inválido'
|
|
106
|
+
|
|
107
|
+
/** Normalize one raw `errors` value entry into a `FieldIssue`.
|
|
108
|
+
* A string → `{message}` (pre-localized, shown verbatim); an object → `{code,params}`. */
|
|
109
|
+
function toFieldIssue(entry: unknown): FieldIssue | undefined {
|
|
110
|
+
if (typeof entry === 'string') {
|
|
111
|
+
const s = entry.trim()
|
|
112
|
+
return s ? { message: s } : undefined
|
|
113
|
+
}
|
|
114
|
+
if (entry && typeof entry === 'object') {
|
|
115
|
+
const e = entry as { code?: unknown; params?: unknown; message?: unknown }
|
|
116
|
+
if (typeof e.message === 'string' && e.message.trim()) return { message: e.message.trim() }
|
|
117
|
+
if (typeof e.code === 'string' && e.code) {
|
|
118
|
+
return {
|
|
119
|
+
code: e.code,
|
|
120
|
+
params: e.params && typeof e.params === 'object' ? (e.params as Record<string, any>) : undefined,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return undefined
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Pull the per-field `errors` map out of an axios error (`err.response.data.errors`)
|
|
129
|
+
* or a bare response body (`{ errors }`), normalizing each field's value to a
|
|
130
|
+
* `FieldIssue[]`. A value may be a single entry or an array; string entries become
|
|
131
|
+
* `{message}`, object entries `{code,params}`. Returns `undefined` when there is no
|
|
132
|
+
* usable map (no `errors`, or nothing normalized). Pure — no i18n, no toast.
|
|
133
|
+
*/
|
|
134
|
+
export function extractFieldErrors(err: unknown): Record<string, FieldIssue[]> | undefined {
|
|
135
|
+
const maybeAxios = (err as { response?: { data?: unknown } } | undefined)?.response?.data
|
|
136
|
+
const data = maybeAxios ?? err
|
|
137
|
+
const errors = (data as { errors?: unknown } | undefined)?.errors
|
|
138
|
+
if (!errors || typeof errors !== 'object' || Array.isArray(errors)) return undefined
|
|
139
|
+
|
|
140
|
+
const out: Record<string, FieldIssue[]> = {}
|
|
141
|
+
for (const [key, raw] of Object.entries(errors as Record<string, unknown>)) {
|
|
142
|
+
const entries = Array.isArray(raw) ? raw : [raw]
|
|
143
|
+
const issues = entries.map(toFieldIssue).filter((i): i is FieldIssue => !!i)
|
|
144
|
+
if (issues.length) out[key] = issues
|
|
145
|
+
}
|
|
146
|
+
return Object.keys(out).length ? out : undefined
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Localize a single `FieldIssue` to a human, Spanish-by-default string using the
|
|
151
|
+
* field `label`. A pre-localized `message` passes through verbatim. Otherwise the
|
|
152
|
+
* `code` is translated via `t('validation.'+code, { defaultValue, label, ...params })`
|
|
153
|
+
* so hosts can override the copy and `{{label}}`/param interpolation still works.
|
|
154
|
+
*/
|
|
155
|
+
export function localizeFieldIssue(issue: FieldIssue, label: string, t: Translate): string {
|
|
156
|
+
if (issue.message) return issue.message
|
|
157
|
+
const code = issue.code ?? ''
|
|
158
|
+
const defaultValue = VALIDATION_DEFAULTS[code] ?? VALIDATION_FALLBACK
|
|
159
|
+
return t(`validation.${code}`, { defaultValue, label, ...(issue.params ?? {}) })
|
|
160
|
+
}
|
|
76
161
|
|
|
77
162
|
/** A dotted, space-free token (e.g. "pos.rate.created") — the shape of an i18n
|
|
78
163
|
* key, as opposed to human prose ("Record created successfully"). Used to
|
package/src/types.ts
CHANGED
|
@@ -374,6 +374,14 @@ export interface ActionFieldDef {
|
|
|
374
374
|
label: string
|
|
375
375
|
type: string
|
|
376
376
|
required?: boolean
|
|
377
|
+
/**
|
|
378
|
+
* Explicit nullability flag served by the kernel (v0.77.1+) from
|
|
379
|
+
* `modelbase.FieldDef.Nullable` (populated as `!Required`). An optional `ref`
|
|
380
|
+
* arrives as `nullable: true`. When present it authoritatively decides whether
|
|
381
|
+
* an empty value should be submitted as `null`; when absent (older hosts) the
|
|
382
|
+
* SDK falls back to type-based heuristics. Additive / optional.
|
|
383
|
+
*/
|
|
384
|
+
nullable?: boolean
|
|
377
385
|
options?: OptionDef[]
|
|
378
386
|
defaultValue?: any
|
|
379
387
|
placeholder?: string
|