@asteby/metacore-runtime-react 31.1.1 → 32.1.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/CHANGELOG.md +27 -0
- package/dist/action-modal-dispatcher.d.ts +20 -0
- package/dist/action-modal-dispatcher.d.ts.map +1 -1
- package/dist/action-modal-dispatcher.js +52 -74
- package/dist/addon-fiber.d.ts +57 -0
- package/dist/addon-fiber.d.ts.map +1 -0
- package/dist/addon-fiber.js +122 -0
- package/dist/addon-loader.d.ts +19 -3
- package/dist/addon-loader.d.ts.map +1 -1
- package/dist/addon-loader.js +36 -37
- package/dist/dialogs/dynamic-record.d.ts.map +1 -1
- package/dist/dialogs/dynamic-record.js +27 -28
- package/dist/dynamic-form-schema.d.ts.map +1 -1
- package/dist/dynamic-form-schema.js +2 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/server-error.d.ts +17 -8
- package/dist/server-error.d.ts.map +1 -1
- package/dist/server-error.js +75 -28
- package/dist/types.d.ts +4 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/validation-catalog.d.ts +8 -0
- package/dist/validation-catalog.d.ts.map +1 -0
- package/dist/validation-catalog.js +59 -0
- package/dist/validator.d.ts +22 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +261 -0
- package/package.json +3 -3
- package/src/__tests__/addon-fiber.test.ts +111 -0
- package/src/__tests__/extract-field-errors.test.ts +25 -1
- package/src/__tests__/prefill-from-record.test.ts +146 -0
- package/src/__tests__/validator.test.ts +70 -0
- package/src/action-modal-dispatcher.tsx +57 -72
- package/src/addon-fiber.ts +155 -0
- package/src/addon-loader.tsx +66 -47
- package/src/dialogs/dynamic-record.tsx +25 -28
- package/src/dynamic-form-schema.ts +3 -2
- package/src/index.ts +24 -0
- package/src/server-error.ts +85 -30
- package/src/types.ts +9 -12
- package/src/validation-catalog.ts +60 -0
- package/src/validator.ts +275 -0
package/dist/addon-loader.js
CHANGED
|
@@ -4,20 +4,15 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
4
4
|
// `remoteEntry.js` as an ESM container, loads the exposed `./register` module,
|
|
5
5
|
// and calls `register(api)` with the AddonAPI injected by the host.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
// the
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// of bundling its own. That's the whole point: it fixes the `useState`-null
|
|
13
|
-
// crash WITHOUT this loader ever touching a share scope manually.
|
|
7
|
+
// Fiber lifecycle (Cordis-style): when `url` (the `?v=` cache-bust) changes,
|
|
8
|
+
// the previous plugin.dispose / returned Disposable run, the host registry
|
|
9
|
+
// unbinds that addonKey, the SW addon-federation cache for that key is
|
|
10
|
+
// purged, and register() runs again against the new remote. The host shell
|
|
11
|
+
// (auth, QueryClient, WebSocket, service worker controller) is not touched.
|
|
14
12
|
import { useEffect, useRef, useState } from 'react';
|
|
15
13
|
import { registerRemotes, loadRemote } from '@module-federation/runtime';
|
|
16
14
|
import { useDeclareAddonLayout } from './addon-layout-context';
|
|
17
|
-
|
|
18
|
-
// which scopes we've registered to avoid redundant `force` churn (each `force`
|
|
19
|
-
// re-register wipes that remote's module cache and logs a runtime warning).
|
|
20
|
-
const registered = new Set();
|
|
15
|
+
import { composeDisposables, disposableFromRegisterResult, markRemoteRegistered, purgeAddonFrontendCache, resolvePluginExports, runDispose, shouldReregisterRemote, } from './addon-fiber';
|
|
21
16
|
// Derive the `loadRemote` id from the scope + exposed module name. MF resolves
|
|
22
17
|
// `"<remoteName>/<expose>"` — e.g. `metacore_tickets/register` for the
|
|
23
18
|
// `"./register"` expose. We strip the leading `./` of the expose path.
|
|
@@ -56,30 +51,19 @@ async function withRuntimeReady(op) {
|
|
|
56
51
|
}
|
|
57
52
|
}
|
|
58
53
|
async function loadAddon(scope, url, module) {
|
|
59
|
-
//
|
|
60
|
-
// the
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// RUNTIME-009 when an addon mounts ahead of the host's federation init —
|
|
66
|
-
// registration is what actually touches the (maybe-uninitialised) runtime.
|
|
67
|
-
if (!registered.has(scope)) {
|
|
68
|
-
await withRuntimeReady(() => registerRemotes([{ name: scope, entry: url, type: 'module' }],
|
|
69
|
-
// `force: true` so a re-registration with a new `?v=` URL (addon
|
|
70
|
-
// hot-swap / version bump) overwrites the stale entry + cache.
|
|
71
|
-
{ force: true }));
|
|
72
|
-
registered.add(scope);
|
|
54
|
+
// Re-register whenever the `?v=` URL changes so a fiber swap actually
|
|
55
|
+
// fetches the new remoteEntry. `force: true` wipes that remote's module
|
|
56
|
+
// cache — without it, loadRemote would keep serving the previous bundle.
|
|
57
|
+
if (shouldReregisterRemote(scope, url)) {
|
|
58
|
+
await withRuntimeReady(() => registerRemotes([{ name: scope, entry: url, type: 'module' }], { force: true }));
|
|
59
|
+
markRemoteRegistered(scope, url);
|
|
73
60
|
}
|
|
74
|
-
// loadRemote("<scope>/<expose>") returns the exposed module namespace (or
|
|
75
|
-
// null if it can't be resolved). No manual share-scope init — the host's
|
|
76
|
-
// federation runtime already initialised it.
|
|
77
61
|
return withRuntimeReady(() => loadRemote(remoteId(scope, module)));
|
|
78
62
|
}
|
|
79
|
-
export function AddonLoader({ scope, url, module = './register', api, fallback = null, onReady, onError, layout, children, }) {
|
|
63
|
+
export function AddonLoader({ scope, url, module = './register', api, hostRegistry, addonKey, unbindKey, fallback = null, onReady, onError, layout, children, }) {
|
|
80
64
|
const [status, setStatus] = useState('loading');
|
|
81
65
|
const [error, setError] = useState(null);
|
|
82
|
-
const
|
|
66
|
+
const disposeRef = useRef(undefined);
|
|
83
67
|
// Propagate the addon's preferred layout to the host shell via context.
|
|
84
68
|
// No-op when `layout` is undefined or `"shell"` (legacy default). Cleanup
|
|
85
69
|
// restores `"shell"` automatically when the loader unmounts, so chrome
|
|
@@ -87,19 +71,26 @@ export function AddonLoader({ scope, url, module = './register', api, fallback =
|
|
|
87
71
|
useDeclareAddonLayout(layout);
|
|
88
72
|
useEffect(() => {
|
|
89
73
|
let cancelled = false;
|
|
74
|
+
const key = addonKey || api.manifest?.key;
|
|
75
|
+
const owner = unbindKey || key;
|
|
90
76
|
(async () => {
|
|
91
77
|
try {
|
|
78
|
+
setStatus('loading');
|
|
79
|
+
if (key)
|
|
80
|
+
await purgeAddonFrontendCache(key);
|
|
92
81
|
const mod = await loadAddon(scope, url, module);
|
|
93
82
|
if (cancelled)
|
|
94
83
|
return;
|
|
95
|
-
const
|
|
96
|
-
if (typeof register !== 'function') {
|
|
84
|
+
const plugin = resolvePluginExports(mod);
|
|
85
|
+
if (typeof plugin.register !== 'function') {
|
|
97
86
|
throw new Error(`Addon "${scope}" module "${module}" has no register() export`);
|
|
98
87
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
88
|
+
// Drop leftover contributions from a previous fiber of this key
|
|
89
|
+
// before the new register() runs (idempotent if none).
|
|
90
|
+
if (owner && hostRegistry)
|
|
91
|
+
hostRegistry.unbind(owner);
|
|
92
|
+
const ret = await Promise.resolve(plugin.register(api));
|
|
93
|
+
disposeRef.current = composeDisposables(disposableFromRegisterResult(ret), plugin.dispose);
|
|
103
94
|
setStatus('ready');
|
|
104
95
|
onReady?.();
|
|
105
96
|
}
|
|
@@ -114,8 +105,16 @@ export function AddonLoader({ scope, url, module = './register', api, fallback =
|
|
|
114
105
|
})();
|
|
115
106
|
return () => {
|
|
116
107
|
cancelled = true;
|
|
108
|
+
const d = disposeRef.current;
|
|
109
|
+
disposeRef.current = undefined;
|
|
110
|
+
void runDispose(d);
|
|
111
|
+
if (owner && hostRegistry)
|
|
112
|
+
hostRegistry.unbind(owner);
|
|
117
113
|
};
|
|
118
|
-
|
|
114
|
+
// api identity is expected to be stable per addon; including it would
|
|
115
|
+
// re-register on every parent render. Fiber identity is (scope, url, module).
|
|
116
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
117
|
+
}, [scope, url, module, addonKey, unbindKey, hostRegistry]);
|
|
119
118
|
if (status === 'loading')
|
|
120
119
|
return _jsx(_Fragment, { children: fallback });
|
|
121
120
|
if (status === 'error')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"dynamic-record.d.ts","sourceRoot":"","sources":["../../src/dialogs/dynamic-record.tsx"],"names":[],"mappings":"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"}
|
|
@@ -23,7 +23,9 @@ import { es } from 'date-fns/locale';
|
|
|
23
23
|
import { ExternalLink, Loader2, CalendarIcon, ChevronDown, Check, Upload, X as XIcon, ScanLine } from 'lucide-react';
|
|
24
24
|
import { BarcodeScanner } from '../barcode-scanner';
|
|
25
25
|
import { useApi } from '../api-context';
|
|
26
|
-
import { toastServerError, extractFieldErrors,
|
|
26
|
+
import { toastServerError, extractFieldErrors, localizeFieldErrorMap } from '../server-error';
|
|
27
|
+
import { validateValues, bagHasErrors } from '../validator';
|
|
28
|
+
import { validationCatalog } from '../validation-catalog';
|
|
27
29
|
import { DynamicSelectField, OptionLead, OptionThumb } from '../dynamic-select-field';
|
|
28
30
|
import { DynamicRelations } from '../dynamic-relations';
|
|
29
31
|
import { useOptionsResolver } from '../use-options-resolver';
|
|
@@ -284,7 +286,7 @@ export function stripHiddenFieldValues(values, fields, mode) {
|
|
|
284
286
|
}
|
|
285
287
|
export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId, endpoint, onSaved, onCreate, onUpdate, defaults, schema, onDelete, onEdit, onOpenFullPage, initialRecord, getImageUrl = identityImageUrl, timeZone, currency, onChange, }) {
|
|
286
288
|
const api = useApi();
|
|
287
|
-
const { t } = useTranslation();
|
|
289
|
+
const { t, i18n } = useTranslation();
|
|
288
290
|
const [modalMeta, setModalMeta] = useState(schema ? schema : null);
|
|
289
291
|
const [relations, setRelations] = useState([]);
|
|
290
292
|
const [record, setRecord] = useState(null);
|
|
@@ -478,24 +480,24 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
478
480
|
const labelForKey = (key) => {
|
|
479
481
|
const f = (modalMeta?.fields ?? []).find(x => x.key === key);
|
|
480
482
|
if (f?.label)
|
|
481
|
-
return f.label;
|
|
483
|
+
return t(f.label, { defaultValue: f.label });
|
|
482
484
|
return key.replace(/[._-]+/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
483
485
|
};
|
|
486
|
+
const lang = i18n.language;
|
|
484
487
|
// Turn a failed submit (422 `errors` map, or a bare `{errors}` body) into
|
|
485
488
|
// inline field errors + a summary toast. When there is no field map, fall
|
|
486
489
|
// back to the existing single cause-carrying toast.
|
|
487
490
|
const handleSubmitError = (err) => {
|
|
488
491
|
const map = extractFieldErrors(err);
|
|
489
492
|
if (map) {
|
|
490
|
-
const
|
|
491
|
-
for (const
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
toast.error(t('dynamic.validation_failed', { defaultValue: 'Revisa los campos marcados' }));
|
|
493
|
+
const labels = {};
|
|
494
|
+
for (const f of modalMeta?.fields ?? [])
|
|
495
|
+
labels[f.key] = labelForKey(f.key);
|
|
496
|
+
setFieldErrors(localizeFieldErrorMap(map, t, { labels, language: lang }));
|
|
497
|
+
toast.error(t('validation.failed', { defaultValue: validationCatalog(lang).failed }));
|
|
496
498
|
return;
|
|
497
499
|
}
|
|
498
|
-
toastServerError(err, { t, fallback: t('dynamic.save_error', { defaultValue: 'No se pudo guardar' }) });
|
|
500
|
+
toastServerError(err, { t, language: lang, fallback: t('dynamic.save_error', { defaultValue: 'No se pudo guardar' }) });
|
|
499
501
|
};
|
|
500
502
|
const handleSubmit = async (e) => {
|
|
501
503
|
e?.preventDefault();
|
|
@@ -507,15 +509,14 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
507
509
|
// fields are gated: a field hidden by its `visible_when` predicate
|
|
508
510
|
// must not block submit even when it is declared required (matching
|
|
509
511
|
// the render, which drops it via the same filter).
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
toast.error(t('dynamic.validation_failed', { defaultValue: 'Revisa los campos marcados' }));
|
|
512
|
+
const visible = filterVisibleFields(modalMeta.fields, mode, formValues);
|
|
513
|
+
const bag = validateValues(visible, formValues);
|
|
514
|
+
if (bagHasErrors(bag)) {
|
|
515
|
+
const labels = {};
|
|
516
|
+
for (const f of visible)
|
|
517
|
+
labels[f.key] = t(f.label, { defaultValue: f.label });
|
|
518
|
+
setFieldErrors(localizeFieldErrorMap(bag, t, { labels, language: lang }));
|
|
519
|
+
toast.error(t('validation.failed', { defaultValue: validationCatalog(lang).failed }));
|
|
519
520
|
return;
|
|
520
521
|
}
|
|
521
522
|
}
|
|
@@ -636,15 +637,13 @@ export function DynamicRecordDialog({ open, onOpenChange, mode, model, recordId,
|
|
|
636
637
|
// then advance. Mirrors handleSubmit's required check but scoped to the step.
|
|
637
638
|
const goNextStep = () => {
|
|
638
639
|
const step = groups[clampedStep];
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
setFieldErrors(missing);
|
|
647
|
-
toast.error(t('dynamic.validation_failed', { defaultValue: 'Revisa los campos marcados' }));
|
|
640
|
+
const bag = validateValues((step?.fields ?? []), formValues);
|
|
641
|
+
if (bagHasErrors(bag)) {
|
|
642
|
+
const labels = {};
|
|
643
|
+
for (const f of step?.fields ?? [])
|
|
644
|
+
labels[f.key] = t(f.label, { defaultValue: f.label });
|
|
645
|
+
setFieldErrors(localizeFieldErrorMap(bag, t, { labels, language: lang }));
|
|
646
|
+
toast.error(t('validation.failed', { defaultValue: validationCatalog(lang).failed }));
|
|
648
647
|
return;
|
|
649
648
|
}
|
|
650
649
|
setFieldErrors({});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"dynamic-form-schema.d.ts","sourceRoot":"","sources":["../src/dynamic-form-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// callers (and unit tests) can use the zod schema without pulling in React or
|
|
3
3
|
// metacore-ui primitives.
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
+
import { fieldValidationOf } from './validator';
|
|
5
6
|
import { resolveValidatorToken } from './use-org-config-bridge';
|
|
6
7
|
/**
|
|
7
8
|
* Built-in validators the SDK knows how to apply by symbolic name. Apps
|
|
@@ -143,7 +144,7 @@ function fieldToZod(field) {
|
|
|
143
144
|
const arr = z.array(row);
|
|
144
145
|
return field.required ? arr.min(1, `${field.label} requiere al menos un renglón`) : arr;
|
|
145
146
|
}
|
|
146
|
-
const v = field
|
|
147
|
+
const v = fieldValidationOf(field);
|
|
147
148
|
const isNumeric = field.type === 'number';
|
|
148
149
|
const isBool = field.type === 'boolean';
|
|
149
150
|
if (isBool) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from './types';
|
|
2
2
|
export { LicenseGate, LicenseExpiryBanner, LicenseStatusBadge, isLicenseOperable, isLicenseBlocking, isPresetEntitled, isTrialExpired, type LicenseGateProps, type LicenseExpiryBannerProps, type LicenseStatusBadgeProps, type LicenseState, type LicenseStatus, type LicenseBranding, } from './license';
|
|
3
3
|
export * from './options-context';
|
|
4
|
-
export { extractServerError, toastServerError, toastServerSuccess, type ExtractedError, type Translate, } from './server-error';
|
|
4
|
+
export { extractServerError, extractFieldErrors, localizeFieldIssue, localizeFieldErrorMap, toastServerError, toastServerSuccess, type ExtractedError, type Translate, type FieldIssue, } from './server-error';
|
|
5
|
+
export { parseRuleString, fieldValidationOf, checkValue, validateValues, bagHasErrors, type ValidationSpec, } from './validator';
|
|
6
|
+
export { VALIDATION_CATALOGS, validationCatalog, validationMessageKey } from './validation-catalog';
|
|
5
7
|
export * from './dynamic-table';
|
|
6
8
|
export { DynamicKanban, type DynamicKanbanProps, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
|
|
7
9
|
export { useStageAutomations, StageAutomationsButton, isTagColumn, automationFieldOptions, groupAutomationsByStage, activeAutomationCount, type StageAutomation, type StageAutomationAction, type StageAutomationActionType, type NewStageAutomation, type UseStageAutomationsResult, type StageAutomationsButtonProps, } from './stage-automations';
|
|
@@ -18,6 +20,7 @@ export { FieldGrid, FieldCell, FieldLabel } from './field-grid';
|
|
|
18
20
|
export { ActionModalDispatcher, type ActionModalProps, } from './action-modal-dispatcher';
|
|
19
21
|
export { ModelActionToolbar, useModelActions, type ModelActionToolbarProps, type ActionPlacement, } from './model-action-toolbar';
|
|
20
22
|
export * from './addon-loader';
|
|
23
|
+
export { PURGE_ADDON_MESSAGE, isAddonFrontendCacheUrl, purgeAddonFrontendCache, resolvePluginExports, composeDisposables, runDispose, type PurgeAddonMessage, type AddonRegisterModule, type ResolvedPlugin, } from './addon-fiber';
|
|
21
24
|
export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, type AddonLayout, type AddonLayoutProviderProps, } from './addon-layout-context';
|
|
22
25
|
export * from './slot';
|
|
23
26
|
export * from './capability-gate';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,OAAO,EACH,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,GACvB,MAAM,WAAW,CAAA;AAClB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,OAAO,EACH,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,GACvB,MAAM,WAAW,CAAA;AAClB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,UAAU,GAClB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACH,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,YAAY,EACZ,KAAK,cAAc,GACtB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AACnG,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,EACvB,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,WAAW,EACX,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,GACnC,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,SAAS,EACT,mBAAmB,EACnB,uBAAuB,EACvB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,GAC9B,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,oBAAoB,GAC5B,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACH,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,GAC/B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACH,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,gBAAgB,GACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACH,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,GAC/B,MAAM,uBAAuB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,kBAAkB,EAClB,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACvB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,cAAc,GACtB,MAAM,eAAe,CAAA;AACtB,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,mBAAmB,EACnB,MAAM,EACN,oBAAoB,EACpB,OAAO,EACP,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,KAAK,EACV,KAAK,wBAAwB,GAChC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,GACzB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,OAAO,EACZ,KAAK,SAAS,GACjB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,yBAAyB,CAAA;AACvC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,EACH,OAAO,EACP,QAAQ,EACR,cAAc,EACd,UAAU,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,UAAU,EACV,SAAS,EACT,UAAU,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,cAAc,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EACH,cAAc,EACd,YAAY,EACZ,WAAW,EACX,UAAU,EACV,KAAK,mBAAmB,EACxB,KAAK,SAAS,IAAI,uBAAuB,GAC5C,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAA;AACxE,YAAY,EAAE,wBAAwB,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC5G,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC/D,YAAY,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,UAAU,EACV,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,sBAAsB,EACtB,6BAA6B,EAC7B,0BAA0B,EAC1B,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,GACtB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,0BAA0B,GAClC,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,GAC1B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,gBAAgB,EAChB,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,aAAa,EACb,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAC/D,YAAY,EACR,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,GACvB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACH,UAAU,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,cAAc,EACd,KAAK,iBAAiB,GACzB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,cAAc,EACd,cAAc,EACd,SAAS,EACT,UAAU,EACV,KAAK,mBAAmB,GAC3B,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,UAAU,EACV,SAAS,EACT,WAAW,EACX,WAAW,EACX,KAAK,eAAe,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,aAAa,EACb,YAAY,EACZ,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
export * from './types';
|
|
7
7
|
export { LicenseGate, LicenseExpiryBanner, LicenseStatusBadge, isLicenseOperable, isLicenseBlocking, isPresetEntitled, isTrialExpired, } from './license';
|
|
8
8
|
export * from './options-context';
|
|
9
|
-
export { extractServerError, toastServerError, toastServerSuccess, } from './server-error';
|
|
9
|
+
export { extractServerError, extractFieldErrors, localizeFieldIssue, localizeFieldErrorMap, toastServerError, toastServerSuccess, } from './server-error';
|
|
10
|
+
export { parseRuleString, fieldValidationOf, checkValue, validateValues, bagHasErrors, } from './validator';
|
|
11
|
+
export { VALIDATION_CATALOGS, validationCatalog, validationMessageKey } from './validation-catalog';
|
|
10
12
|
export * from './dynamic-table';
|
|
11
13
|
export { DynamicKanban, deriveStages, groupByStage, isTransitionAllowed, applyOptimisticMove, selectCardColumns, UNASSIGNED_LANE, } from './dynamic-kanban';
|
|
12
14
|
export { useStageAutomations, StageAutomationsButton, isTagColumn, automationFieldOptions, groupAutomationsByStage, activeAutomationCount, } from './stage-automations';
|
|
@@ -23,6 +25,7 @@ export { FieldGrid, FieldCell, FieldLabel } from './field-grid';
|
|
|
23
25
|
export { ActionModalDispatcher, } from './action-modal-dispatcher';
|
|
24
26
|
export { ModelActionToolbar, useModelActions, } from './model-action-toolbar';
|
|
25
27
|
export * from './addon-loader';
|
|
28
|
+
export { PURGE_ADDON_MESSAGE, isAddonFrontendCacheUrl, purgeAddonFrontendCache, resolvePluginExports, composeDisposables, runDispose, } from './addon-fiber';
|
|
26
29
|
export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, } from './addon-layout-context';
|
|
27
30
|
export * from './slot';
|
|
28
31
|
export * from './capability-gate';
|
package/dist/server-error.d.ts
CHANGED
|
@@ -41,12 +41,18 @@ export interface FieldIssue {
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function extractFieldErrors(err: unknown): Record<string, FieldIssue[]> | undefined;
|
|
43
43
|
/**
|
|
44
|
-
* Localize a single `FieldIssue` to a human
|
|
45
|
-
*
|
|
46
|
-
* `code` is translated via `t('validation.'+
|
|
47
|
-
*
|
|
44
|
+
* Localize a single `FieldIssue` to a human string using the field `label` and
|
|
45
|
+
* the operator's language. A pre-localized `message` passes through verbatim.
|
|
46
|
+
* Otherwise `code` is translated via `t('validation.'+key)` with a catalog
|
|
47
|
+
* default (es unless `language` is `en` / `en-*`).
|
|
48
48
|
*/
|
|
49
|
-
export declare function localizeFieldIssue(issue: FieldIssue, label: string, t: Translate): string;
|
|
49
|
+
export declare function localizeFieldIssue(issue: FieldIssue, label: string, t: Translate, language?: string): string;
|
|
50
|
+
/** Localize a whole 422 `errors` map into `{ [field]: firstMessage }` using
|
|
51
|
+
* optional per-key labels (already translated) and the current language. */
|
|
52
|
+
export declare function localizeFieldErrorMap(map: Record<string, FieldIssue[]>, t: Translate, opts?: {
|
|
53
|
+
labels?: Record<string, string>;
|
|
54
|
+
language?: string;
|
|
55
|
+
}): Record<string, string>;
|
|
50
56
|
/**
|
|
51
57
|
* Toast a successful mutation/action response, LOCALIZED. The kernel/host often
|
|
52
58
|
* returns a hardcoded English `message` ("Record created successfully"); echoing
|
|
@@ -61,12 +67,15 @@ export declare function toastServerSuccess(data: unknown, opts?: {
|
|
|
61
67
|
}): void;
|
|
62
68
|
/**
|
|
63
69
|
* Toast a server/network error, surfacing the REAL cause as the description
|
|
64
|
-
* instead of a bare generic line.
|
|
65
|
-
*
|
|
66
|
-
*
|
|
70
|
+
* instead of a bare generic line. A 422 `{errors:{field:[{code}]}}` bag is
|
|
71
|
+
* localized per-field (never `[object Object]` / English "validation failed").
|
|
72
|
+
* Pass `language` (i18n.language) so catalogs match the operator's lang;
|
|
73
|
+
* pass `labels` so field keys map to translated headers.
|
|
67
74
|
*/
|
|
68
75
|
export declare function toastServerError(err: unknown, opts?: {
|
|
69
76
|
t?: Translate;
|
|
70
77
|
fallback?: string;
|
|
78
|
+
language?: string;
|
|
79
|
+
labels?: Record<string, string>;
|
|
71
80
|
}): void;
|
|
72
81
|
//# sourceMappingURL=server-error.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-error.d.ts","sourceRoot":"","sources":["../src/server-error.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server-error.d.ts","sourceRoot":"","sources":["../src/server-error.ts"],"names":[],"mappings":"AAcA,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC3B,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAA;IACb;;gDAE4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;AA8BD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,GAAG,cAAc,CAuBtF;AAED;;gFAEgF;AAChF,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,KAAK,MAAM,CAAA;AAUvG;8EAC8E;AAC9E,MAAM,WAAW,UAAU;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AA6BD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,GAAG,SAAS,CAazF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAC9B,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,MAAM,EACb,CAAC,EAAE,SAAS,EACZ,QAAQ,CAAC,EAAE,MAAM,GAClB,MAAM,CAMR;AAED;6EAC6E;AAC7E,wBAAgB,qBAAqB,CACjC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,EACjC,CAAC,EAAE,SAAS,EACZ,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9D,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAUD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAC9B,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;IAAE,CAAC,CAAC,EAAE,SAAS,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,IAAI,CAUN;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC5B,GAAG,EAAE,OAAO,EACZ,IAAI,CAAC,EAAE;IAAE,CAAC,CAAC,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAChG,IAAI,CA4BN"}
|
package/dist/server-error.js
CHANGED
|
@@ -10,17 +10,36 @@
|
|
|
10
10
|
// or report it. This module keeps the headline but ALSO surfaces the cause as
|
|
11
11
|
// the toast description, in ONE place so every call site behaves identically.
|
|
12
12
|
import { toast } from 'sonner';
|
|
13
|
+
import { validationCatalog, validationMessageKey } from './validation-catalog';
|
|
14
|
+
function formatIssueEntry(v) {
|
|
15
|
+
if (v == null)
|
|
16
|
+
return '';
|
|
17
|
+
if (typeof v === 'string')
|
|
18
|
+
return v;
|
|
19
|
+
if (typeof v === 'object' && 'code' in v) {
|
|
20
|
+
const e = v;
|
|
21
|
+
if (typeof e.message === 'string' && e.message.trim())
|
|
22
|
+
return e.message.trim();
|
|
23
|
+
if (typeof e.code === 'string' && e.code)
|
|
24
|
+
return e.code;
|
|
25
|
+
}
|
|
26
|
+
return String(v);
|
|
27
|
+
}
|
|
13
28
|
/** Flattens a validation `errors` payload (string | string[] | field→msgs map)
|
|
14
|
-
* into a single newline-joined string, or undefined when empty.
|
|
29
|
+
* into a single newline-joined string, or undefined when empty. Object entries
|
|
30
|
+
* `{code, params}` render as the code (never `[object Object]`). */
|
|
15
31
|
function joinErrors(errors) {
|
|
16
32
|
if (!errors)
|
|
17
33
|
return undefined;
|
|
18
34
|
if (typeof errors === 'string')
|
|
19
35
|
return errors || undefined;
|
|
20
36
|
if (Array.isArray(errors))
|
|
21
|
-
return errors.
|
|
37
|
+
return errors.map(formatIssueEntry).filter(Boolean).join('\n') || undefined;
|
|
22
38
|
if (typeof errors === 'object') {
|
|
23
|
-
const parts = Object.entries(errors).map(([k, v]) =>
|
|
39
|
+
const parts = Object.entries(errors).map(([k, v]) => {
|
|
40
|
+
const body = Array.isArray(v) ? v.map(formatIssueEntry).filter(Boolean).join(', ') : formatIssueEntry(v);
|
|
41
|
+
return body ? `${k}: ${body}` : '';
|
|
42
|
+
}).filter(Boolean);
|
|
24
43
|
return parts.join('\n') || undefined;
|
|
25
44
|
}
|
|
26
45
|
return undefined;
|
|
@@ -59,17 +78,12 @@ export function extractServerError(err, fallbackTitle) {
|
|
|
59
78
|
return { title: fallbackTitle, description: raw.trim() };
|
|
60
79
|
return { title: fallbackTitle };
|
|
61
80
|
}
|
|
62
|
-
/** Spanish
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
not_found: 'El {{label}} seleccionado no existe',
|
|
69
|
-
duplicate: 'Ya existe un registro con ese {{label}}',
|
|
70
|
-
invalid_type: 'El campo {{label}} tiene un formato inválido',
|
|
71
|
-
};
|
|
72
|
-
const VALIDATION_FALLBACK = '{{label}}: valor inválido';
|
|
81
|
+
/** Spanish/English catalogs live in `validation-catalog.ts`. Hosts override any
|
|
82
|
+
* key via i18next `validation.<code>`. `{{label}}` and code params (min/max/
|
|
83
|
+
* allowed/ref/expected) interpolate through i18next. */
|
|
84
|
+
function humanizeKey(k) {
|
|
85
|
+
return k.replace(/[._-]+/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
86
|
+
}
|
|
73
87
|
/** Normalize one raw `errors` value entry into a `FieldIssue`.
|
|
74
88
|
* A string → `{message}` (pre-localized, shown verbatim); an object → `{code,params}`. */
|
|
75
89
|
function toFieldIssue(entry) {
|
|
@@ -113,17 +127,28 @@ export function extractFieldErrors(err) {
|
|
|
113
127
|
return Object.keys(out).length ? out : undefined;
|
|
114
128
|
}
|
|
115
129
|
/**
|
|
116
|
-
* Localize a single `FieldIssue` to a human
|
|
117
|
-
*
|
|
118
|
-
* `code` is translated via `t('validation.'+
|
|
119
|
-
*
|
|
130
|
+
* Localize a single `FieldIssue` to a human string using the field `label` and
|
|
131
|
+
* the operator's language. A pre-localized `message` passes through verbatim.
|
|
132
|
+
* Otherwise `code` is translated via `t('validation.'+key)` with a catalog
|
|
133
|
+
* default (es unless `language` is `en` / `en-*`).
|
|
120
134
|
*/
|
|
121
|
-
export function localizeFieldIssue(issue, label, t) {
|
|
135
|
+
export function localizeFieldIssue(issue, label, t, language) {
|
|
122
136
|
if (issue.message)
|
|
123
137
|
return issue.message;
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
138
|
+
const key = validationMessageKey(issue.code ?? '', issue.params);
|
|
139
|
+
const cat = validationCatalog(language);
|
|
140
|
+
const defaultValue = cat[key] ?? cat.fallback ?? '{{label}}: valor inválido';
|
|
141
|
+
return t(`validation.${key}`, { defaultValue, label, ...(issue.params ?? {}) });
|
|
142
|
+
}
|
|
143
|
+
/** Localize a whole 422 `errors` map into `{ [field]: firstMessage }` using
|
|
144
|
+
* optional per-key labels (already translated) and the current language. */
|
|
145
|
+
export function localizeFieldErrorMap(map, t, opts) {
|
|
146
|
+
const out = {};
|
|
147
|
+
for (const [k, issues] of Object.entries(map)) {
|
|
148
|
+
const label = opts?.labels?.[k] ?? humanizeKey(k);
|
|
149
|
+
out[k] = localizeFieldIssue(issues[0], label, t, opts?.language);
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
127
152
|
}
|
|
128
153
|
/** A dotted, space-free token (e.g. "pos.rate.created") — the shape of an i18n
|
|
129
154
|
* key, as opposed to human prose ("Record created successfully"). Used to
|
|
@@ -153,14 +178,36 @@ export function toastServerSuccess(data, opts) {
|
|
|
153
178
|
}
|
|
154
179
|
/**
|
|
155
180
|
* Toast a server/network error, surfacing the REAL cause as the description
|
|
156
|
-
* instead of a bare generic line.
|
|
157
|
-
*
|
|
158
|
-
*
|
|
181
|
+
* instead of a bare generic line. A 422 `{errors:{field:[{code}]}}` bag is
|
|
182
|
+
* localized per-field (never `[object Object]` / English "validation failed").
|
|
183
|
+
* Pass `language` (i18n.language) so catalogs match the operator's lang;
|
|
184
|
+
* pass `labels` so field keys map to translated headers.
|
|
159
185
|
*/
|
|
160
186
|
export function toastServerError(err, opts) {
|
|
161
187
|
const t = opts?.t;
|
|
188
|
+
const lang = opts?.language;
|
|
189
|
+
const cat = validationCatalog(lang);
|
|
190
|
+
const map = extractFieldErrors(err);
|
|
191
|
+
if (map) {
|
|
192
|
+
const localized = t
|
|
193
|
+
? localizeFieldErrorMap(map, t, { labels: opts?.labels, language: lang })
|
|
194
|
+
: undefined;
|
|
195
|
+
const title = t
|
|
196
|
+
? t('validation.failed', { defaultValue: cat.failed })
|
|
197
|
+
: cat.failed;
|
|
198
|
+
const description = localized
|
|
199
|
+
? Object.values(localized).join('\n')
|
|
200
|
+
: Object.entries(map)
|
|
201
|
+
.map(([k, issues]) => `${k}: ${issues[0]?.code ?? issues[0]?.message ?? ''}`)
|
|
202
|
+
.join('\n');
|
|
203
|
+
toast.error(title, description ? { description } : undefined);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
162
206
|
const fallback = opts?.fallback ?? (t ? t('common.error', { defaultValue: 'Something went wrong' }) : 'Something went wrong');
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
207
|
+
const extracted = extractServerError(err, fallback);
|
|
208
|
+
let shownTitle = t ? t(extracted.title, { defaultValue: extracted.title }) : extracted.title;
|
|
209
|
+
if (extracted.title === 'validation failed' || extracted.title === 'validation.failed') {
|
|
210
|
+
shownTitle = t ? t('validation.failed', { defaultValue: cat.failed }) : cat.failed;
|
|
211
|
+
}
|
|
212
|
+
toast.error(shownTitle, extracted.description ? { description: extracted.description } : undefined);
|
|
166
213
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -291,11 +291,10 @@ export interface ColumnDefinition {
|
|
|
291
291
|
*/
|
|
292
292
|
ref?: string;
|
|
293
293
|
/**
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
* reference resolved through the OrgConfigProvider.
|
|
294
|
+
* Write-time rules the SDK also pre-flights. Object form `{regex,min,max,custom}`
|
|
295
|
+
* or a Laravel / go-playground string (`required|min:2|email`).
|
|
297
296
|
*/
|
|
298
|
-
validation?: FieldValidation;
|
|
297
|
+
validation?: FieldValidation | string;
|
|
299
298
|
/**
|
|
300
299
|
* Declared schema for a jsonb line-items column (kernel v3 `item_fields`).
|
|
301
300
|
* Each entry describes one sub-field of the array's row objects: a `key`
|
|
@@ -415,7 +414,7 @@ export interface ActionFieldDef {
|
|
|
415
414
|
defaultValue?: any;
|
|
416
415
|
placeholder?: string;
|
|
417
416
|
searchEndpoint?: string;
|
|
418
|
-
validation?: FieldValidation;
|
|
417
|
+
validation?: FieldValidation | string;
|
|
419
418
|
widget?: FieldWidget | string;
|
|
420
419
|
/**
|
|
421
420
|
* FK target model — same semantics as ColumnDefinition.ref. When
|