@resistdesign/voltra 3.0.0-alpha.33 → 3.0.0-alpha.34
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 +72 -2
- package/api/index.js +49 -36
- package/app/forms/Engine.d.ts +3 -0
- package/app/forms/UI.d.ts +7 -1
- package/app/forms/core/createAutoField.d.ts +11 -3
- package/app/forms/core/createFormRenderer.d.ts +3 -2
- package/app/forms/core/mergeSuites.d.ts +3 -2
- package/app/forms/core/resolveSuite.d.ts +2 -1
- package/app/forms/core/types.d.ts +20 -9
- package/app/forms/types.d.ts +34 -7
- package/app/index.js +21 -12
- package/app/utils/ApplicationState.d.ts +1 -1
- package/build/index.js +2 -2
- package/chunk-4PV5LPTT.js +1144 -0
- package/{chunk-FQMZMCXU.js → chunk-RUVFOXCR.js} +1 -1
- package/chunk-TJFTWPXQ.js +39 -0
- package/{chunk-LGM75I6P.js → chunk-WTD5BBJP.js} +223 -38
- package/common/Logging/Utils.d.ts +0 -9
- package/common/TypeParsing/TypeInfo.d.ts +20 -0
- package/common/TypeParsing/Validation.d.ts +152 -22
- package/common/index.js +6 -7
- package/iac/packs/auth.d.ts +10 -4
- package/iac-packs/index.d.ts +1 -0
- package/native/forms/UI.d.ts +8 -2
- package/native/forms/createNativeFormRenderer.d.ts +1 -1
- package/native/forms/index.d.ts +16 -0
- package/native/forms/suite.d.ts +1 -1
- package/native/index.js +71 -40
- package/native/testing/react-native.d.ts +33 -15
- package/native/utils/index.d.ts +13 -1
- package/package.json +1 -1
- package/web/forms/UI.d.ts +8 -2
- package/web/forms/createWebFormRenderer.d.ts +1 -1
- package/web/forms/suite.d.ts +1 -1
- package/web/index.js +234 -113
- package/web/utils/Route.d.ts +9 -3
- package/web/utils/index.d.ts +1 -0
- package/chunk-G5CLUK4Y.js +0 -621
- package/chunk-IWRHGGGH.js +0 -10
- package/chunk-WELZGQDJ.js +0 -456
package/README.md
CHANGED
|
@@ -131,8 +131,10 @@ EasyLayout now has:
|
|
|
131
131
|
- Client routing: `examples/routing/app-routing.ts`
|
|
132
132
|
- Backend API routing: `examples/api/backend-routing.ts`
|
|
133
133
|
- Forms: `examples/forms/`
|
|
134
|
+
- `examples/forms/auto-form-validation-customization.tsx`
|
|
134
135
|
- Layout: `examples/layout/`
|
|
135
136
|
- Common types: `examples/common/types.ts`
|
|
137
|
+
- `examples/common/typeinfo-validation.ts`
|
|
136
138
|
- Build-time parsing: `examples/build/type-parsing.ts`
|
|
137
139
|
|
|
138
140
|
### Template syntax
|
|
@@ -191,12 +193,13 @@ const coords = layout.computeNativeCoords({
|
|
|
191
193
|
|
|
192
194
|
## Routing (Web + Native)
|
|
193
195
|
|
|
194
|
-
Voltra routing
|
|
196
|
+
Voltra routing uses the same `Route` API across app/web/native.
|
|
197
|
+
Use the platform barrel for root `Route` so runtime mechanics are auto-wired.
|
|
195
198
|
|
|
196
199
|
Reference example: `examples/routing/app-routing.ts`
|
|
197
200
|
|
|
198
201
|
```tsx
|
|
199
|
-
import { Route } from "@resistdesign/voltra/
|
|
202
|
+
import { Route } from "@resistdesign/voltra/web";
|
|
200
203
|
|
|
201
204
|
<Route>
|
|
202
205
|
<Route path="/" exact>
|
|
@@ -211,6 +214,16 @@ import { Route } from "@resistdesign/voltra/app";
|
|
|
211
214
|
</Route>;
|
|
212
215
|
```
|
|
213
216
|
|
|
217
|
+
```tsx
|
|
218
|
+
import { Route } from "@resistdesign/voltra/native";
|
|
219
|
+
|
|
220
|
+
<Route>
|
|
221
|
+
<Route path="/" exact>
|
|
222
|
+
<HomeScreen />
|
|
223
|
+
</Route>
|
|
224
|
+
</Route>;
|
|
225
|
+
```
|
|
226
|
+
|
|
214
227
|
How it works:
|
|
215
228
|
|
|
216
229
|
- Root `<Route>` (no `path`) is provider mode.
|
|
@@ -311,6 +324,63 @@ Renderers emit actions via:
|
|
|
311
324
|
|
|
312
325
|
Use these to wire modals, selectors, or editors without baking UI into the core engine.
|
|
313
326
|
|
|
327
|
+
### Centralized Validation
|
|
328
|
+
|
|
329
|
+
All TypeInfo data-item validation can be run directly from `@resistdesign/voltra/common`:
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
import { validateTypeInfoDataItem } from "@resistdesign/voltra/common";
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
`validateTypeInfoDataItem` supports:
|
|
336
|
+
|
|
337
|
+
- field-level `customValidatorMap` callbacks that return `ErrorDescriptor`
|
|
338
|
+
- `tags.validation` field options:
|
|
339
|
+
- `validateHidden`
|
|
340
|
+
- `validateReadonly`
|
|
341
|
+
- `emptyArrayIsValid`
|
|
342
|
+
|
|
343
|
+
AutoForm passes validation through the same centralized logic and supports:
|
|
344
|
+
|
|
345
|
+
- `customValidatorMap` for app-specific rules
|
|
346
|
+
- `translateValidationErrorCode` for UI-facing messages
|
|
347
|
+
- multiple value-level errors per field
|
|
348
|
+
- per-index array item errors for array fields
|
|
349
|
+
|
|
350
|
+
Validation error maps can include both value-level errors and array item errors:
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
{
|
|
354
|
+
errorMap: {
|
|
355
|
+
title: [
|
|
356
|
+
{ code: "MISSING_FIELD_VALUE" },
|
|
357
|
+
{ code: "VALUE_DOES_NOT_MATCH_PATTERN" }
|
|
358
|
+
],
|
|
359
|
+
tags: [
|
|
360
|
+
{ code: "INVALID_TYPE" },
|
|
361
|
+
{
|
|
362
|
+
itemErrorMap: {
|
|
363
|
+
0: [{ code: "NOT_A_STRING" }],
|
|
364
|
+
2: [
|
|
365
|
+
{ code: "NOT_A_STRING" },
|
|
366
|
+
{ code: "INVALID_CUSTOM_TYPE" }
|
|
367
|
+
]
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
]
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
- Field-level multiple errors are represented by multiple `ErrorDescriptor` entries in the field array.
|
|
376
|
+
- Array item errors are represented by `itemErrorMap[index] = ErrorDescriptor[]`.
|
|
377
|
+
|
|
378
|
+
Error-code constants are split by purpose:
|
|
379
|
+
|
|
380
|
+
- `PRIMITIVE_ERROR_MESSAGE_CONSTANTS` keys follow `typeof` (`string`, `number`, `boolean`)
|
|
381
|
+
- `DENIED_TYPE_OPERATIONS` keys follow `TypeOperation` (`CREATE`, `READ`, `UPDATE`, `DELETE`)
|
|
382
|
+
- `ERROR_MESSAGE_CONSTANTS` exposes canonical code-keyed entries (for example `NOT_A_STRING`, `DENIED_TYPE_OPERATION_CREATE`)
|
|
383
|
+
|
|
314
384
|
## Docs Site
|
|
315
385
|
|
|
316
386
|
The docs site is both reference documentation and a canonical usage example.
|
package/api/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { ERROR_MESSAGE_CONSTANTS, validateTypeOperationAllowed, getValidityValue, validateTypeInfoValue, validateTypeInfoFieldValue } from '../chunk-LGM75I6P.js';
|
|
2
1
|
import { ITEM_RELATIONSHIP_DAC_RESOURCE_NAME, ComparisonOperators } from '../chunk-7AMEFPPP.js';
|
|
3
|
-
import '../chunk-
|
|
2
|
+
import { getNoErrorDescriptor, getErrorDescriptor, ERROR_MESSAGE_CONSTANTS, validateTypeOperationAllowed, getValidityValue, validateTypeInfoValue, validateTypeInfoFieldValue } from '../chunk-WTD5BBJP.js';
|
|
4
3
|
import { mergeStringPaths, getPathString, getPathArray } from '../chunk-GYWRAW3Y.js';
|
|
5
4
|
import '../chunk-I2KLQ2HA.js';
|
|
6
5
|
import { QueryCommand, GetItemCommand, BatchGetItemCommand, BatchWriteItemCommand, DynamoDBClient, PutItemCommand, UpdateItemCommand, DeleteItemCommand, ScanCommand } from '@aws-sdk/client-dynamodb';
|
|
@@ -4361,6 +4360,9 @@ var getFilterTypeInfoDataItemsBySearchCriteria = (searchCriteria, items, typeInf
|
|
|
4361
4360
|
for (const currentItem of items) {
|
|
4362
4361
|
if (typeof currentItem === "object" && currentItem !== null) {
|
|
4363
4362
|
let meetsCriteria = true;
|
|
4363
|
+
if (logicalOperator === "OR" /* OR */ && fieldCriteria.length > 0) {
|
|
4364
|
+
meetsCriteria = false;
|
|
4365
|
+
}
|
|
4364
4366
|
for (const fieldCriterion of fieldCriteria) {
|
|
4365
4367
|
const { fieldName } = fieldCriterion;
|
|
4366
4368
|
const { array: isArrayType, typeReference } = fields[fieldName] || {};
|
|
@@ -6477,7 +6479,7 @@ var validateSearchFields = (typeInfoName, typeInfoMap, searchFields = [], disall
|
|
|
6477
6479
|
const results = {
|
|
6478
6480
|
typeName: typeInfoName,
|
|
6479
6481
|
valid: true,
|
|
6480
|
-
error:
|
|
6482
|
+
error: getNoErrorDescriptor(),
|
|
6481
6483
|
errorMap: {}
|
|
6482
6484
|
};
|
|
6483
6485
|
if (typeInfo) {
|
|
@@ -6487,7 +6489,7 @@ var validateSearchFields = (typeInfoName, typeInfoMap, searchFields = [], disall
|
|
|
6487
6489
|
if (!customOperator && (!operator || !Object.values(ComparisonOperators).includes(operator))) {
|
|
6488
6490
|
results.valid = false;
|
|
6489
6491
|
results.errorMap[fieldName] = [
|
|
6490
|
-
SEARCH_VALIDATION_ERRORS.INVALID_OPERATOR
|
|
6492
|
+
getErrorDescriptor(SEARCH_VALIDATION_ERRORS.INVALID_OPERATOR)
|
|
6491
6493
|
];
|
|
6492
6494
|
} else {
|
|
6493
6495
|
const tIF = fields[fieldName];
|
|
@@ -6497,12 +6499,14 @@ var validateSearchFields = (typeInfoName, typeInfoMap, searchFields = [], disall
|
|
|
6497
6499
|
if (denyRead) {
|
|
6498
6500
|
results.valid = false;
|
|
6499
6501
|
results.errorMap[fieldName] = [
|
|
6500
|
-
SEARCH_VALIDATION_ERRORS.INVALID_FIELD
|
|
6502
|
+
getErrorDescriptor(SEARCH_VALIDATION_ERRORS.INVALID_FIELD)
|
|
6501
6503
|
];
|
|
6502
6504
|
} else if (typeReference) {
|
|
6503
6505
|
results.valid = false;
|
|
6504
6506
|
results.errorMap[fieldName] = [
|
|
6505
|
-
|
|
6507
|
+
getErrorDescriptor(
|
|
6508
|
+
SEARCH_VALIDATION_ERRORS.RELATIONAL_FIELDS_NOT_ALLOWED
|
|
6509
|
+
)
|
|
6506
6510
|
];
|
|
6507
6511
|
} else {
|
|
6508
6512
|
if (operator && OPERATORS_WITHOUT_VALUES.has(operator)) {
|
|
@@ -6514,7 +6518,7 @@ var validateSearchFields = (typeInfoName, typeInfoMap, searchFields = [], disall
|
|
|
6514
6518
|
tVO,
|
|
6515
6519
|
tIF,
|
|
6516
6520
|
typeInfoMap,
|
|
6517
|
-
|
|
6521
|
+
true,
|
|
6518
6522
|
true,
|
|
6519
6523
|
customValidators,
|
|
6520
6524
|
"READ" /* READ */,
|
|
@@ -6532,14 +6536,14 @@ var validateSearchFields = (typeInfoName, typeInfoMap, searchFields = [], disall
|
|
|
6532
6536
|
} else {
|
|
6533
6537
|
results.valid = false;
|
|
6534
6538
|
results.errorMap[fieldName] = [
|
|
6535
|
-
SEARCH_VALIDATION_ERRORS.INVALID_FIELD
|
|
6539
|
+
getErrorDescriptor(SEARCH_VALIDATION_ERRORS.INVALID_FIELD)
|
|
6536
6540
|
];
|
|
6537
6541
|
}
|
|
6538
6542
|
}
|
|
6539
6543
|
}
|
|
6540
6544
|
} else {
|
|
6541
6545
|
results.valid = false;
|
|
6542
|
-
results.error = SEARCH_VALIDATION_ERRORS.INVALID_TYPE_INFO;
|
|
6546
|
+
results.error = getErrorDescriptor(SEARCH_VALIDATION_ERRORS.INVALID_TYPE_INFO);
|
|
6543
6547
|
}
|
|
6544
6548
|
return results;
|
|
6545
6549
|
};
|
|
@@ -6555,7 +6559,7 @@ var validateRelationshipItem = (relationshipItem, omitFields) => {
|
|
|
6555
6559
|
const results = {
|
|
6556
6560
|
typeName: fromTypeName,
|
|
6557
6561
|
valid: true,
|
|
6558
|
-
error:
|
|
6562
|
+
error: getNoErrorDescriptor(),
|
|
6559
6563
|
errorMap: {}
|
|
6560
6564
|
};
|
|
6561
6565
|
if (typeof relationshipItem === "object" && relationshipItem !== null) {
|
|
@@ -6568,24 +6572,34 @@ var validateRelationshipItem = (relationshipItem, omitFields) => {
|
|
|
6568
6572
|
universalRKV in relationshipItem && typeof relationshipItem[universalRKV] !== "string" || omitRKV && universalRKV in relationshipItem
|
|
6569
6573
|
) {
|
|
6570
6574
|
results.valid = false;
|
|
6571
|
-
results.error =
|
|
6575
|
+
results.error = getErrorDescriptor(
|
|
6576
|
+
TYPE_INFO_ORM_RELATIONSHIP_ERRORS.INVALID_RELATIONSHIP_ITEM
|
|
6577
|
+
);
|
|
6572
6578
|
results.errorMap[rKV] = [
|
|
6573
|
-
|
|
6579
|
+
getErrorDescriptor(
|
|
6580
|
+
TYPE_INFO_ORM_RELATIONSHIP_ERRORS.INVALID_RELATIONSHIP_ITEM_FIELD
|
|
6581
|
+
)
|
|
6574
6582
|
];
|
|
6575
6583
|
} else if (
|
|
6576
6584
|
// Missing Field
|
|
6577
6585
|
!omitRKV && (!(universalRKV in relationshipItem) || !relationshipItem[universalRKV])
|
|
6578
6586
|
) {
|
|
6579
6587
|
results.valid = false;
|
|
6580
|
-
results.error =
|
|
6588
|
+
results.error = getErrorDescriptor(
|
|
6589
|
+
TYPE_INFO_ORM_RELATIONSHIP_ERRORS.INVALID_RELATIONSHIP_ITEM
|
|
6590
|
+
);
|
|
6581
6591
|
results.errorMap[rKV] = [
|
|
6582
|
-
|
|
6592
|
+
getErrorDescriptor(
|
|
6593
|
+
TYPE_INFO_ORM_RELATIONSHIP_ERRORS.MISSING_RELATIONSHIP_ITEM_FIELD
|
|
6594
|
+
)
|
|
6583
6595
|
];
|
|
6584
6596
|
}
|
|
6585
6597
|
}
|
|
6586
6598
|
} else {
|
|
6587
6599
|
results.valid = false;
|
|
6588
|
-
results.error =
|
|
6600
|
+
results.error = getErrorDescriptor(
|
|
6601
|
+
TYPE_INFO_ORM_RELATIONSHIP_ERRORS.INVALID_RELATIONSHIP_ITEM
|
|
6602
|
+
);
|
|
6589
6603
|
}
|
|
6590
6604
|
return results;
|
|
6591
6605
|
};
|
|
@@ -6594,13 +6608,7 @@ var validateRelationshipItem = (relationshipItem, omitFields) => {
|
|
|
6594
6608
|
var removeNonexistentFieldsFromSelectedFields = (typeInfo = {}, selectedFields) => {
|
|
6595
6609
|
if (Array.isArray(selectedFields)) {
|
|
6596
6610
|
const { fields = {} } = typeInfo;
|
|
6597
|
-
|
|
6598
|
-
for (const tIF in fields) {
|
|
6599
|
-
if (selectedFields.includes(tIF)) {
|
|
6600
|
-
cleanSelectFields.push(tIF);
|
|
6601
|
-
}
|
|
6602
|
-
}
|
|
6603
|
-
return cleanSelectFields;
|
|
6611
|
+
return selectedFields.filter((field) => Boolean(fields[field]));
|
|
6604
6612
|
} else {
|
|
6605
6613
|
return selectedFields;
|
|
6606
6614
|
}
|
|
@@ -6608,14 +6616,10 @@ var removeNonexistentFieldsFromSelectedFields = (typeInfo = {}, selectedFields)
|
|
|
6608
6616
|
var removeTypeReferenceFieldsFromSelectedFields = (typeInfo = {}, selectedFields) => {
|
|
6609
6617
|
if (Array.isArray(selectedFields)) {
|
|
6610
6618
|
const { fields = {} } = typeInfo;
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
cleanSelectFields.push(tIF);
|
|
6616
|
-
}
|
|
6617
|
-
}
|
|
6618
|
-
return cleanSelectFields;
|
|
6619
|
+
return selectedFields.filter((field) => {
|
|
6620
|
+
const typeInfoField = fields[field];
|
|
6621
|
+
return typeInfoField && typeof typeInfoField.typeReference === "undefined";
|
|
6622
|
+
});
|
|
6619
6623
|
} else {
|
|
6620
6624
|
return selectedFields;
|
|
6621
6625
|
}
|
|
@@ -7583,7 +7587,7 @@ var TypeInfoORMService = class {
|
|
|
7583
7587
|
const results = {
|
|
7584
7588
|
typeName,
|
|
7585
7589
|
valid: !!typeInfo,
|
|
7586
|
-
error: !!typeInfo ?
|
|
7590
|
+
error: !!typeInfo ? getNoErrorDescriptor() : getErrorDescriptor(ERROR_MESSAGE_CONSTANTS.TYPE_DOES_NOT_EXIST),
|
|
7587
7591
|
errorMap: {}
|
|
7588
7592
|
};
|
|
7589
7593
|
const {
|
|
@@ -7602,7 +7606,7 @@ var TypeInfoORMService = class {
|
|
|
7602
7606
|
const existingError = results.errorMap[oE] ?? [];
|
|
7603
7607
|
results.errorMap[oE] = existingError ? [...existingError, ...operationErrorMap[oE]] : operationErrorMap[oE];
|
|
7604
7608
|
}
|
|
7605
|
-
if (!operationValid && operationError) {
|
|
7609
|
+
if (!operationValid && operationError.code !== ERROR_MESSAGE_CONSTANTS.NONE) {
|
|
7606
7610
|
results.error = operationError;
|
|
7607
7611
|
}
|
|
7608
7612
|
if (!results.valid) {
|
|
@@ -7701,7 +7705,7 @@ var TypeInfoORMService = class {
|
|
|
7701
7705
|
const relationshipValidationResults = {
|
|
7702
7706
|
typeName: fromTypeName,
|
|
7703
7707
|
valid: false,
|
|
7704
|
-
error: "INVALID_RELATIONSHIP" /* INVALID_RELATIONSHIP
|
|
7708
|
+
error: getErrorDescriptor("INVALID_RELATIONSHIP" /* INVALID_RELATIONSHIP */),
|
|
7705
7709
|
errorMap: {}
|
|
7706
7710
|
};
|
|
7707
7711
|
throw relationshipValidationResults;
|
|
@@ -8111,7 +8115,9 @@ var TypeInfoORMService = class {
|
|
|
8111
8115
|
const validationResults = {
|
|
8112
8116
|
typeName,
|
|
8113
8117
|
valid: false,
|
|
8114
|
-
error:
|
|
8118
|
+
error: getErrorDescriptor(
|
|
8119
|
+
"NO_PRIMARY_FIELD_VALUE_SUPPLIED" /* NO_PRIMARY_FIELD_VALUE_SUPPLIED */
|
|
8120
|
+
),
|
|
8115
8121
|
errorMap: {}
|
|
8116
8122
|
};
|
|
8117
8123
|
throw validationResults;
|
|
@@ -8460,6 +8466,13 @@ var getHeadersWithCORS = (origin = "", corsPatterns = []) => {
|
|
|
8460
8466
|
};
|
|
8461
8467
|
|
|
8462
8468
|
// src/common/Logging/Utils.ts
|
|
8469
|
+
var stringifyForLog = (value) => {
|
|
8470
|
+
try {
|
|
8471
|
+
return JSON.stringify(value, null, 2);
|
|
8472
|
+
} catch (error) {
|
|
8473
|
+
return "[Unserializable]";
|
|
8474
|
+
}
|
|
8475
|
+
};
|
|
8463
8476
|
var logFunctionCall = async (label, args, functionRef, enabled) => {
|
|
8464
8477
|
if (enabled) {
|
|
8465
8478
|
console.log(
|
|
@@ -8467,7 +8480,7 @@ var logFunctionCall = async (label, args, functionRef, enabled) => {
|
|
|
8467
8480
|
"INPUT" /* INPUT */,
|
|
8468
8481
|
label,
|
|
8469
8482
|
":",
|
|
8470
|
-
|
|
8483
|
+
stringifyForLog(args)
|
|
8471
8484
|
);
|
|
8472
8485
|
}
|
|
8473
8486
|
try {
|
|
@@ -8478,7 +8491,7 @@ var logFunctionCall = async (label, args, functionRef, enabled) => {
|
|
|
8478
8491
|
"OUTPUT" /* OUTPUT */,
|
|
8479
8492
|
label,
|
|
8480
8493
|
":",
|
|
8481
|
-
|
|
8494
|
+
stringifyForLog(result)
|
|
8482
8495
|
);
|
|
8483
8496
|
}
|
|
8484
8497
|
return result;
|
package/app/forms/Engine.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { TypeInfo } from "../../common/TypeParsing/TypeInfo";
|
|
7
7
|
import { TypeOperation } from "../../common/TypeParsing/TypeInfo";
|
|
8
|
+
import { type FieldValueValidatorMap } from "../../common/TypeParsing/Validation";
|
|
8
9
|
import type { FormController, FormValues } from "./types";
|
|
9
10
|
/**
|
|
10
11
|
* Hook that derives form state and field controllers from type metadata.
|
|
@@ -17,4 +18,6 @@ import type { FormController, FormValues } from "./types";
|
|
|
17
18
|
export declare const useFormEngine: (initialValues: FormValues | undefined, typeInfo: TypeInfo, options?: {
|
|
18
19
|
/** Operation to evaluate when deriving field state. */
|
|
19
20
|
operation?: TypeOperation;
|
|
21
|
+
/** Optional custom validators keyed by field name. */
|
|
22
|
+
customValidatorMap?: FieldValueValidatorMap;
|
|
20
23
|
}) => FormController;
|
package/app/forms/UI.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { FC } from "react";
|
|
7
7
|
import type { ReactElement } from "react";
|
|
8
8
|
import type { TypeInfo, TypeOperation } from "../../common/TypeParsing/TypeInfo";
|
|
9
|
-
import type { AutoFieldProps, CustomTypeActionPayload, FormController, FormValues, RelationActionPayload } from "./types";
|
|
9
|
+
import type { AutoFieldProps, CustomValidatorMap, CustomTypeActionPayload, FormController, FormValues, RelationActionPayload, TranslateValidationErrorCode } from "./types";
|
|
10
10
|
import type { ResolvedSuite } from "./core/types";
|
|
11
11
|
/**
|
|
12
12
|
* Renderer contract used by shared AutoForm components.
|
|
@@ -33,6 +33,8 @@ export interface AutoFormViewProps {
|
|
|
33
33
|
onRelationAction?: (payload: RelationActionPayload) => void;
|
|
34
34
|
/** Optional custom type action handler. */
|
|
35
35
|
onCustomTypeAction?: (payload: CustomTypeActionPayload) => void;
|
|
36
|
+
/** Optional translator for validation error descriptors. */
|
|
37
|
+
translateValidationErrorCode?: TranslateValidationErrorCode;
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
40
|
* Render a form UI from a prepared form controller.
|
|
@@ -63,6 +65,10 @@ export interface AutoFormProps {
|
|
|
63
65
|
operation?: TypeOperation;
|
|
64
66
|
/** Disable the submit button when true. */
|
|
65
67
|
submitDisabled?: boolean;
|
|
68
|
+
/** Optional translator for validation error descriptors. */
|
|
69
|
+
translateValidationErrorCode?: TranslateValidationErrorCode;
|
|
70
|
+
/** Optional custom validators keyed by field name. */
|
|
71
|
+
customValidatorMap?: CustomValidatorMap;
|
|
66
72
|
}
|
|
67
73
|
/**
|
|
68
74
|
* Build a controller from type metadata and render an auto form.
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Factory for AutoField that delegates rendering to a resolved suite.
|
|
5
5
|
*/
|
|
6
|
+
import { type FC, type ReactElement } from "react";
|
|
6
7
|
import type { TypeInfoField } from "../../../common/TypeParsing/TypeInfo";
|
|
8
|
+
import { type ErrorDescriptor, type ArrayItemErrorMap } from "../../../common/TypeParsing/Validation";
|
|
7
9
|
import type { CustomTypeActionPayload, FieldValue, RelationActionPayload, ResolvedSuite } from "./types";
|
|
8
10
|
/**
|
|
9
11
|
* Input props for AutoField render delegation.
|
|
@@ -17,8 +19,14 @@ export type AutoFieldInput = {
|
|
|
17
19
|
value: FieldValue | undefined;
|
|
18
20
|
/** Change handler for the field value. */
|
|
19
21
|
onChange: (value: FieldValue) => void;
|
|
20
|
-
/** Optional error
|
|
21
|
-
error?:
|
|
22
|
+
/** Optional error descriptor to display under the field. */
|
|
23
|
+
error?: ErrorDescriptor;
|
|
24
|
+
/** Optional value-level errors for the field. */
|
|
25
|
+
errors?: ErrorDescriptor[];
|
|
26
|
+
/** Optional per-index errors for array fields. */
|
|
27
|
+
arrayItemErrorMap?: ArrayItemErrorMap;
|
|
28
|
+
/** Optional translator for validation error descriptors. */
|
|
29
|
+
translateValidationErrorCode?: (error: ErrorDescriptor) => string;
|
|
22
30
|
/** Disables the field UI when true. */
|
|
23
31
|
disabled?: boolean;
|
|
24
32
|
/** Optional callback for relation actions. */
|
|
@@ -32,4 +40,4 @@ export type AutoFieldInput = {
|
|
|
32
40
|
* @param suite - Resolved component suite.
|
|
33
41
|
* @returns AutoField renderer function.
|
|
34
42
|
*/
|
|
35
|
-
export declare const createAutoField: <RenderOutput =
|
|
43
|
+
export declare const createAutoField: <RenderOutput = ReactElement>(suite: ResolvedSuite<RenderOutput>) => FC<AutoFieldInput>;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Factory for building form renderers backed by component suites.
|
|
5
5
|
*/
|
|
6
|
+
import type { ReactElement } from "react";
|
|
6
7
|
import type { ResolvedSuite, ComponentSuite } from "./types";
|
|
7
8
|
import { type AutoFieldInput } from "./createAutoField";
|
|
8
9
|
/**
|
|
@@ -11,11 +12,11 @@ import { type AutoFieldInput } from "./createAutoField";
|
|
|
11
12
|
* @param options - Suite configuration.
|
|
12
13
|
* @returns Renderer helpers tied to resolved suites.
|
|
13
14
|
*/
|
|
14
|
-
export declare const createFormRenderer: <RenderOutput =
|
|
15
|
+
export declare const createFormRenderer: <RenderOutput = ReactElement>(options: {
|
|
15
16
|
fallbackSuite: ComponentSuite<RenderOutput>;
|
|
16
17
|
suite?: ComponentSuite<RenderOutput>;
|
|
17
18
|
}) => {
|
|
18
|
-
AutoField: (
|
|
19
|
+
AutoField: import("react").FC<AutoFieldInput>;
|
|
19
20
|
suite: ResolvedSuite<RenderOutput>;
|
|
20
21
|
};
|
|
21
22
|
/**
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Helpers for composing component suites.
|
|
5
5
|
*/
|
|
6
|
+
import type { ReactElement } from "react";
|
|
6
7
|
import type { ComponentSuite, FieldKind, FieldRenderer } from "./types";
|
|
7
8
|
/**
|
|
8
9
|
* Deep-merge component suites, allowing overrides for renderers and primitives.
|
|
@@ -11,7 +12,7 @@ import type { ComponentSuite, FieldKind, FieldRenderer } from "./types";
|
|
|
11
12
|
* @param overrides - Partial suite overrides.
|
|
12
13
|
* @returns Merged suite.
|
|
13
14
|
*/
|
|
14
|
-
export declare const mergeSuites: <RenderOutput =
|
|
15
|
+
export declare const mergeSuites: <RenderOutput = ReactElement>(base: ComponentSuite<RenderOutput>, overrides: ComponentSuite<RenderOutput>) => ComponentSuite<RenderOutput>;
|
|
15
16
|
/**
|
|
16
17
|
* Convenience helper to override a single renderer.
|
|
17
18
|
*
|
|
@@ -19,4 +20,4 @@ export declare const mergeSuites: <RenderOutput = unknown>(base: ComponentSuite<
|
|
|
19
20
|
* @param renderer - Replacement renderer.
|
|
20
21
|
* @returns Suite with renderer override.
|
|
21
22
|
*/
|
|
22
|
-
export declare const withRendererOverride: <RenderOutput =
|
|
23
|
+
export declare const withRendererOverride: <RenderOutput = ReactElement>(kind: FieldKind, renderer: FieldRenderer<RenderOutput>) => ComponentSuite<RenderOutput>;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Resolve a component suite by merging overrides with fallback defaults.
|
|
5
5
|
*/
|
|
6
|
+
import type { ReactElement } from "react";
|
|
6
7
|
import type { ComponentSuite, ResolvedSuite } from "./types";
|
|
7
8
|
/**
|
|
8
9
|
* Merge a fallback suite with overrides and ensure completeness.
|
|
@@ -11,4 +12,4 @@ import type { ComponentSuite, ResolvedSuite } from "./types";
|
|
|
11
12
|
* @param fallback - Default suite providing full coverage.
|
|
12
13
|
* @returns Fully resolved suite with all renderers present.
|
|
13
14
|
*/
|
|
14
|
-
export declare const resolveSuite: <RenderOutput =
|
|
15
|
+
export declare const resolveSuite: <RenderOutput = ReactElement>(overrides: ComponentSuite<RenderOutput> | undefined, fallback: ComponentSuite<RenderOutput>) => ResolvedSuite<RenderOutput>;
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Core, platform-agnostic types for form rendering.
|
|
5
5
|
*/
|
|
6
|
+
import type { ComponentType, ReactElement } from "react";
|
|
6
7
|
import type { LiteralValue, TypeInfoDataItem, TypeInfoField } from "../../../common/TypeParsing/TypeInfo";
|
|
8
|
+
import type { ArrayItemErrorMap, ErrorDescriptor } from "../../../common/TypeParsing/Validation";
|
|
7
9
|
import type { ItemRelationshipInfoType } from "../../../common/ItemRelationshipInfoTypes";
|
|
8
10
|
/**
|
|
9
11
|
* Supported field kinds for renderer selection.
|
|
@@ -77,8 +79,14 @@ export type FieldRenderContext<RenderOutput = unknown> = {
|
|
|
77
79
|
required: boolean;
|
|
78
80
|
/** True when the field UI should be disabled. */
|
|
79
81
|
disabled: boolean;
|
|
80
|
-
/** Optional error
|
|
81
|
-
error?:
|
|
82
|
+
/** Optional error descriptor to display under the field. */
|
|
83
|
+
error?: ErrorDescriptor;
|
|
84
|
+
/** Optional value-level errors for the field. */
|
|
85
|
+
errors?: ErrorDescriptor[];
|
|
86
|
+
/** Optional per-index errors for array fields. */
|
|
87
|
+
arrayItemErrorMap?: ArrayItemErrorMap;
|
|
88
|
+
/** Translate an error descriptor to a user-facing message. */
|
|
89
|
+
translateValidationErrorCode: (error: ErrorDescriptor) => string;
|
|
82
90
|
/** Current value for the field. */
|
|
83
91
|
value: FieldValue | undefined;
|
|
84
92
|
/** Change handler for the field value. */
|
|
@@ -103,24 +111,27 @@ export type FieldRenderContext<RenderOutput = unknown> = {
|
|
|
103
111
|
fieldKey: string;
|
|
104
112
|
value: FieldValue | undefined;
|
|
105
113
|
onChange: (value: FieldValue) => void;
|
|
106
|
-
error?:
|
|
114
|
+
error?: ErrorDescriptor;
|
|
115
|
+
errors?: ErrorDescriptor[];
|
|
116
|
+
arrayItemErrorMap?: ArrayItemErrorMap;
|
|
117
|
+
translateValidationErrorCode?: (error: ErrorDescriptor) => string;
|
|
107
118
|
disabled?: boolean;
|
|
108
119
|
onRelationAction?: (payload: RelationActionPayload) => void;
|
|
109
120
|
onCustomTypeAction?: (payload: CustomTypeActionPayload) => void;
|
|
110
121
|
}) => RenderOutput;
|
|
111
122
|
};
|
|
112
123
|
/**
|
|
113
|
-
* Renderer
|
|
124
|
+
* Renderer component for a single field kind.
|
|
114
125
|
*/
|
|
115
|
-
export type FieldRenderer<RenderOutput =
|
|
126
|
+
export type FieldRenderer<RenderOutput = ReactElement> = ComponentType<FieldRenderContext<RenderOutput>>;
|
|
116
127
|
/**
|
|
117
128
|
* Optional primitive component contract for suites.
|
|
118
129
|
*/
|
|
119
|
-
export type PrimitiveComponent<Props, RenderOutput =
|
|
130
|
+
export type PrimitiveComponent<Props, RenderOutput = ReactElement> = (props: Props) => RenderOutput;
|
|
120
131
|
/**
|
|
121
132
|
* Primitive components that suites may override.
|
|
122
133
|
*/
|
|
123
|
-
export type PrimitiveComponents<RenderOutput =
|
|
134
|
+
export type PrimitiveComponents<RenderOutput = ReactElement> = {
|
|
124
135
|
/** Root container for the form view. */
|
|
125
136
|
FormRoot: PrimitiveComponent<{
|
|
126
137
|
children: RenderOutput;
|
|
@@ -151,7 +162,7 @@ export type PrimitiveComponents<RenderOutput = unknown> = {
|
|
|
151
162
|
/**
|
|
152
163
|
* Suite definition with optional renderers/primitives.
|
|
153
164
|
*/
|
|
154
|
-
export type ComponentSuite<RenderOutput =
|
|
165
|
+
export type ComponentSuite<RenderOutput = ReactElement> = {
|
|
155
166
|
/** Field renderers keyed by kind. */
|
|
156
167
|
renderers: Partial<Record<FieldKind, FieldRenderer<RenderOutput>>>;
|
|
157
168
|
/** Optional primitive component overrides. */
|
|
@@ -160,7 +171,7 @@ export type ComponentSuite<RenderOutput = unknown> = {
|
|
|
160
171
|
/**
|
|
161
172
|
* Fully resolved suite with required renderers.
|
|
162
173
|
*/
|
|
163
|
-
export type ResolvedSuite<RenderOutput =
|
|
174
|
+
export type ResolvedSuite<RenderOutput = ReactElement> = {
|
|
164
175
|
/** Field renderers keyed by kind. */
|
|
165
176
|
renderers: Record<FieldKind, FieldRenderer<RenderOutput>>;
|
|
166
177
|
/** Optional primitive component overrides. */
|
package/app/forms/types.d.ts
CHANGED
|
@@ -4,8 +4,17 @@
|
|
|
4
4
|
* Types for the form generation system.
|
|
5
5
|
*/
|
|
6
6
|
import type { TypeInfo, TypeInfoDataItem, TypeInfoField, TypeOperation } from "../../common/TypeParsing/TypeInfo";
|
|
7
|
+
import type { ArrayErrorDescriptorCollection, ArrayItemErrorMap, ErrorDescriptor, FieldValueValidatorMap, TypeInfoValidationResults } from "../../common/TypeParsing/Validation";
|
|
7
8
|
import type { CustomTypeActionPayload, RelationActionPayload } from "./core/types";
|
|
8
9
|
export * from "./core/types";
|
|
10
|
+
/**
|
|
11
|
+
* Translates validation error descriptors into UI messages.
|
|
12
|
+
*/
|
|
13
|
+
export type TranslateValidationErrorCode = (error: ErrorDescriptor) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Optional custom field validators keyed by field name.
|
|
16
|
+
*/
|
|
17
|
+
export type CustomValidatorMap = FieldValueValidatorMap;
|
|
9
18
|
/**
|
|
10
19
|
* Loose map of form values keyed by field.
|
|
11
20
|
*/
|
|
@@ -47,8 +56,14 @@ export interface AutoFieldProps {
|
|
|
47
56
|
value: FormValue | undefined;
|
|
48
57
|
/** Change handler for the field value. */
|
|
49
58
|
onChange: (value: FormValue) => void;
|
|
50
|
-
/** Optional error
|
|
51
|
-
error?:
|
|
59
|
+
/** Optional primary error descriptor for convenience/backward compatibility. */
|
|
60
|
+
error?: ErrorDescriptor;
|
|
61
|
+
/** Optional list of value-level errors for the field. */
|
|
62
|
+
errors?: ErrorDescriptor[];
|
|
63
|
+
/** Optional per-index errors for array fields. */
|
|
64
|
+
arrayItemErrorMap?: ArrayItemErrorMap;
|
|
65
|
+
/** Optional translator from error descriptor to user-facing message. */
|
|
66
|
+
translateValidationErrorCode?: TranslateValidationErrorCode;
|
|
52
67
|
/** Disables the field UI when true. */
|
|
53
68
|
disabled?: boolean;
|
|
54
69
|
/** Optional callback for relation actions. */
|
|
@@ -82,9 +97,21 @@ export type FormFieldController = {
|
|
|
82
97
|
value: FormValue | undefined;
|
|
83
98
|
/** Change handler for the field value. */
|
|
84
99
|
onChange: (value: FormValue) => void;
|
|
85
|
-
/** Optional error
|
|
86
|
-
error?:
|
|
100
|
+
/** Optional primary error descriptor for the field. */
|
|
101
|
+
error?: ErrorDescriptor;
|
|
102
|
+
/** Optional list of value-level errors for the field. */
|
|
103
|
+
errors?: ErrorDescriptor[];
|
|
104
|
+
/** Optional per-index errors for array fields. */
|
|
105
|
+
arrayItemErrorMap?: ArrayItemErrorMap;
|
|
87
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* Validation errors keyed by field and represented as descriptors/codes.
|
|
109
|
+
*/
|
|
110
|
+
export type FormErrorMap = Record<string, (ErrorDescriptor | ArrayErrorDescriptorCollection)[]>;
|
|
111
|
+
/**
|
|
112
|
+
* Input map used to set form errors, accepting descriptors or raw codes.
|
|
113
|
+
*/
|
|
114
|
+
export type FormErrorInputMap = Record<string, ErrorDescriptor | string | ErrorDescriptor[] | ArrayItemErrorMap | (ErrorDescriptor | ArrayErrorDescriptorCollection)[]>;
|
|
88
115
|
/**
|
|
89
116
|
* Controller for a form instance and its fields.
|
|
90
117
|
*/
|
|
@@ -98,13 +125,13 @@ export type FormController = {
|
|
|
98
125
|
/** Current form values keyed by field. */
|
|
99
126
|
values: FormValues;
|
|
100
127
|
/** Validation errors keyed by field. */
|
|
101
|
-
errors:
|
|
128
|
+
errors: FormErrorMap;
|
|
102
129
|
/** Derived controllers for each field. */
|
|
103
130
|
fields: FormFieldController[];
|
|
104
131
|
/** Update a field value by key. */
|
|
105
132
|
setFieldValue: (key: string, value: FormValue) => void;
|
|
106
133
|
/** Validate the form and return success. */
|
|
107
|
-
validate: () =>
|
|
134
|
+
validate: () => TypeInfoValidationResults;
|
|
108
135
|
/** Override form errors with a provided map. */
|
|
109
|
-
setErrors: (errors:
|
|
136
|
+
setErrors: (errors: FormErrorInputMap) => void;
|
|
110
137
|
};
|