openxiangda-devkit-core 2.41.2 → 2.48.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.
@@ -1,10 +1,11 @@
1
- import { parseNativeUniqueKeys } from 'openxiangda-contracts';
1
+ import { validateUserCandidateReferences, validateWorkflowUserCandidateBindings, UserCandidateContractError } from 'openxiangda-contracts';
2
+ import { parseNativeUniqueKeys, DATA_SUBTABLE_MAX_TOTAL_ROWS } from 'openxiangda-contracts';
2
3
  import { validateManagedConcurrency } from 'openxiangda-contracts/native-compiler';
3
4
  import { normalizeDecimalReservationEventDeclaration, decimalReservationEventContext } from 'openxiangda-contracts';
4
5
  import { materializeApplicationModules } from './application-model.js';
5
6
  import { nativeFieldRequiresCreateInputV2 } from 'openxiangda-contracts/native-compiler';
6
7
  import { NativeDecimalLifecycleContractError, validateDecimalReservationLifecycles } from 'openxiangda-contracts/native-compiler';
7
- import { SCHEMA_VERSIONS, DATA_AUDIT_METADATA_FIELDS, isDataAuditMetadataField, isDataSystemSortField, DATA_FIELD_TYPES, PLATFORM_EVENT_TYPES_V2, nativePlatformCapabilityCatalog, validateDataResource, validateWorkflowInstanceCommandPolicies, validateWorkflowTaskPages, validateWorkflowAssignmentRoutingBindings, WORKFLOW_SUMMARY_MAX_FIELDS, WORKFLOW_SUMMARY_TEXT_LONG_MAX_BYTES, isWorkflowSummaryFieldType, } from 'openxiangda-contracts';
8
+ import { SCHEMA_VERSIONS, DATA_AUDIT_METADATA_FIELDS, isDataAuditMetadataField, isDataSystemSortField, DATA_FIELD_TYPES, PLATFORM_EVENT_TYPES_V2, nativePlatformCapabilityCatalog, validateDataResource, validateWorkflowInstanceCommandPolicies, validateWorkflowTaskPages, validateWorkflowCommandHandlers, validateWorkflowAssignmentRoutingBindings, WORKFLOW_SUMMARY_MAX_FIELDS, WORKFLOW_SUMMARY_TEXT_LONG_MAX_BYTES, isWorkflowSummaryFieldType, } from 'openxiangda-contracts';
8
9
  import { Ajv2020 } from 'ajv/dist/2020.js';
9
10
  import { CronExpressionParser } from 'cron-parser';
10
11
  import { fieldNullable, supportsGeneratedMutation, } from './field-codec.js';
@@ -966,6 +967,7 @@ export function validateAppConfig(value) {
966
967
  secretNames.add(name);
967
968
  secretEnvs.add(env);
968
969
  });
970
+ const authz = object(config.authz);
969
971
  const data = object(config.data);
970
972
  if (config.data !== undefined && !Array.isArray(data.resources)) {
971
973
  diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCES_REQUIRED', 'data.resources 必须是数组', 'data.resources'));
@@ -995,6 +997,15 @@ export function validateAppConfig(value) {
995
997
  resources.forEach((resource, index) => {
996
998
  const resourceRecord = object(resource);
997
999
  const fields = object(resourceRecord.schema).fields;
1000
+ try {
1001
+ validateUserCandidateReferences((Array.isArray(fields) ? fields : []), new Set((Array.isArray(authz.roles) ? authz.roles : []).map(item => string(object(item).code))), new Set((Array.isArray(authz.scopeDimensions) ? authz.scopeDimensions : []).map(item => string(object(item).code))), `data.resources[${index}].schema.fields`);
1002
+ }
1003
+ catch (error) {
1004
+ if (!(error instanceof UserCandidateContractError))
1005
+ throw error;
1006
+ const issue = error;
1007
+ diagnostics.push(diagnostic(issue.code, '人员候选必须引用同一应用声明的职责、范围和兼容字段', issue.pointer));
1008
+ }
998
1009
  (Array.isArray(fields) ? fields : []).forEach((rawField, fieldIndex) => {
999
1010
  const field = object(rawField);
1000
1011
  const source = object(field.source);
@@ -1048,7 +1059,6 @@ export function validateAppConfig(value) {
1048
1059
  });
1049
1060
  });
1050
1061
  }
1051
- const authz = object(config.authz);
1052
1062
  const declaredCapabilities = Array.isArray(authz.capabilities)
1053
1063
  ? authz.capabilities
1054
1064
  : [];
@@ -1069,7 +1079,13 @@ export function validateAppConfig(value) {
1069
1079
  const mutationOwner = string(object(resource.surface).mutationOwner) || 'native';
1070
1080
  if (['native', 'queued-command'].includes(mutationOwner))
1071
1081
  continue;
1082
+ const recordDeletionCapabilities = object(resource.recordDeletion).delete;
1072
1083
  for (const operation of ['create', 'update', 'delete']) {
1084
+ // This capability authorizes the server's controlled deletion entry;
1085
+ // ordinary Native mutations still belong to the declared owner.
1086
+ if (operation === 'delete' && Array.isArray(recordDeletionCapabilities) &&
1087
+ recordDeletionCapabilities.length > 0)
1088
+ continue;
1073
1089
  const capability = string(object(resource.capabilities)[operation]);
1074
1090
  if (capability) {
1075
1091
  forbiddenNativeMutationCapabilities.set(capability, {
@@ -2397,7 +2413,7 @@ export function validateAppConfig(value) {
2397
2413
  }
2398
2414
  }
2399
2415
  }
2400
- for (const error of validateWorkflowDefinition(definition)) {
2416
+ for (const error of [...validateWorkflowDefinition(definition), ...validateWorkflowCommandHandlers(definition, object(config.backend).operations)]) {
2401
2417
  diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_DEFINITION_INVALID', error, path));
2402
2418
  }
2403
2419
  const policyResource = object((Array.isArray(data.resources) ? data.resources : []).find(resource => object(resource).code === definition.subject?.resourceCode));
@@ -2474,6 +2490,18 @@ export function validateAppConfig(value) {
2474
2490
  for (const error of validateWorkflowBinding(definition, binding)) {
2475
2491
  diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_BINDING_INVALID', error, path));
2476
2492
  }
2493
+ try {
2494
+ const resource = (Array.isArray(data.resources) ? data.resources : []).map(object).find(item => item.code === definition.subject.resourceCode);
2495
+ const fields = object(resource?.schema).fields;
2496
+ validateWorkflowUserCandidateBindings(definition, binding, new Map((Array.isArray(fields) ? fields : []).map(item => [String(object(item).code), item])), `${path}.bindings`, new Map((Array.isArray(data.resources) ? data.resources : []).map(object).map(resource => [String(resource.code),
2497
+ new Map((Array.isArray(object(resource.schema).fields) ? object(resource.schema).fields : []).map(field => [field.code, field]))])));
2498
+ }
2499
+ catch (error) {
2500
+ if (!(error instanceof UserCandidateContractError))
2501
+ throw error;
2502
+ const issue = error;
2503
+ diagnostics.push(diagnostic(issue.code, '流程选人必须引用原人员字段及固定范围事实,办理页不能修改选人范围', issue.pointer));
2504
+ }
2477
2505
  activeRoutingBindings.push(binding);
2478
2506
  for (const entry of Object.values(binding.bindings || {})) {
2479
2507
  if (entry.provider === 'application_provider' &&
@@ -2771,10 +2799,24 @@ function validateBackendOperations(rawOperations, declaredResources, declaredRes
2771
2799
  'workflow',
2772
2800
  'roleAssertions',
2773
2801
  'decimalReservation',
2802
+ 'dataCommands',
2803
+ 'recordEdit',
2774
2804
  ]);
2775
2805
  let invalid = !isRecord(operation.platformAccess) ||
2776
2806
  Object.keys(access).length === 0 ||
2777
2807
  Object.keys(access).some(key => !allowedAccessKeys.has(key));
2808
+ if (access.dataCommands !== undefined) {
2809
+ const commands = object(access.dataCommands);
2810
+ invalid ||= commands.mode !== 'recoverable-native' || Object.keys(commands).some(key => key !== 'mode');
2811
+ }
2812
+ if (access.recordEdit !== undefined) {
2813
+ const edit = object(access.recordEdit);
2814
+ const fields = Array.isArray(edit.fieldCodes) ? edit.fieldCodes : [];
2815
+ invalid ||= !isRecord(access.recordEdit) || !string(edit.resourceCode) || fields.length < 1 || fields.length > 200 ||
2816
+ new Set(fields).size !== fields.length || fields.some(field => !string(field)) ||
2817
+ Object.keys(edit).some(key => !['resourceCode', 'fieldCodes'].includes(key)) ||
2818
+ object(access.dataCommands).mode !== 'recoverable-native';
2819
+ }
2778
2820
  if (access.roleAssertions !== undefined) {
2779
2821
  const assertion = object(access.roleAssertions);
2780
2822
  const roles = Array.isArray(assertion.roleCodes) ? assertion.roleCodes : [];
@@ -2790,6 +2832,7 @@ function validateBackendOperations(rawOperations, declaredResources, declaredRes
2790
2832
  'employeeNumber',
2791
2833
  'primaryDepartment',
2792
2834
  'departments',
2835
+ ...(directory.mode === 'current-initiator' ? ['phone'] : []),
2793
2836
  ]);
2794
2837
  invalid ||=
2795
2838
  !['current-initiator', 'selected-user'].includes(String(directory.mode)) ||
@@ -2891,7 +2934,12 @@ function validateBackendOperations(rawOperations, declaredResources, declaredRes
2891
2934
  workflowCodes.length > 16 ||
2892
2935
  new Set(workflowCodes).size !== workflowCodes.length ||
2893
2936
  workflowCodes.some(code => !declaredWorkflowCodes.has(code)) ||
2894
- Object.keys(workflow).some(key => key !== 'codes');
2937
+ Object.keys(workflow).some(key => !['codes', 'businessCommands'].includes(key));
2938
+ if (workflow.businessCommands !== undefined) {
2939
+ const commands = workflow.businessCommands;
2940
+ invalid ||= !Array.isArray(commands) || commands.length === 0 || commands.length > 3 ||
2941
+ new Set(commands).size !== commands.length || commands.some(command => !['approve', 'reject', 'withdraw'].includes(command));
2942
+ }
2895
2943
  }
2896
2944
  if (access.decimalReservation !== undefined) {
2897
2945
  const reservation = object(access.decimalReservation);
@@ -4226,7 +4274,10 @@ function validateCapabilityClosure(appCode, config, declarations, diagnostics) {
4226
4274
  if (code)
4227
4275
  catalogOwners.set(code, 'authz.capabilities');
4228
4276
  }
4229
- for (const [resourceIndex, rawResource] of (Array.isArray(data.resources) ? data.resources : []).entries()) {
4277
+ const resources = Array.isArray(data.resources) ? data.resources : [];
4278
+ // Register every real definition before inspecting field references. A field
4279
+ // may reference a later resource without claiming that resource's catalog.
4280
+ for (const rawResource of resources) {
4230
4281
  const resource = object(rawResource);
4231
4282
  const resourceOwner = `data.resources.${string(resource.code)}`;
4232
4283
  Object.values(object(resource.capabilities)).forEach(value => {
@@ -4234,18 +4285,42 @@ function validateCapabilityClosure(appCode, config, declarations, diagnostics) {
4234
4285
  registerCapabilityOwner(code, resourceOwner, catalogOwners, diagnostics);
4235
4286
  catalog.add(code);
4236
4287
  });
4288
+ }
4289
+ const definedCapabilities = new Set(catalog);
4290
+ for (const [resourceIndex, rawResource] of resources.entries()) {
4291
+ const resource = object(rawResource);
4292
+ const resourceOwner = `data.resources.${string(resource.code)}`;
4293
+ for (const mode of ['read', 'create']) {
4294
+ const list = object(resource.recordComments)[mode];
4295
+ for (const capability of Array.isArray(list) ? list : []) {
4296
+ references.push({ capability: string(capability), path: `data.resources[${resourceIndex}].recordComments.${mode}` });
4297
+ }
4298
+ }
4299
+ const printRead = object(resource.recordPrint).read;
4300
+ const recordDelete = object(resource.recordDeletion).delete;
4301
+ for (const capability of Array.isArray(recordDelete) ? recordDelete : []) {
4302
+ references.push({ capability: string(capability), path: `data.resources[${resourceIndex}].recordDeletion.delete` });
4303
+ }
4304
+ for (const capability of Array.isArray(printRead) ? printRead : []) {
4305
+ references.push({ capability: string(capability), path: `data.resources[${resourceIndex}].recordPrint.read` });
4306
+ }
4307
+ const historyRead = object(resource.workflowHistory).read;
4308
+ for (const capability of Array.isArray(historyRead) ? historyRead : []) {
4309
+ references.push({ capability: string(capability), path: `data.resources[${resourceIndex}].workflowHistory.read` });
4310
+ }
4237
4311
  Object.entries(object(resource.fieldPolicies)).forEach(([fieldCode, rawPolicy]) => {
4238
4312
  const policy = object(rawPolicy);
4239
4313
  for (const key of ['read', 'create', 'update']) {
4240
4314
  for (const value of Array.isArray(policy[key]) ? policy[key] : []) {
4241
4315
  const code = string(value);
4242
- if (isDataAuditMetadataField(fieldCode)) {
4243
- references.push({
4244
- capability: code,
4245
- path: `data.resources[${resourceIndex}].fieldPolicies.${fieldCode}.${key}`,
4246
- });
4316
+ references.push({
4317
+ capability: code,
4318
+ path: `data.resources[${resourceIndex}].fieldPolicies.${fieldCode}.${key}`,
4319
+ });
4320
+ if (isDataAuditMetadataField(fieldCode) || definedCapabilities.has(code))
4247
4321
  continue;
4248
- }
4322
+ // Preserve resource-owned implicit field capabilities. Reusing an
4323
+ // explicit/CRUD definition is different from two implicit owners.
4249
4324
  registerCapabilityOwner(code, resourceOwner, catalogOwners, diagnostics);
4250
4325
  catalog.add(code);
4251
4326
  }
@@ -4444,6 +4519,7 @@ export function materializeDataResource(appCode, declaration) {
4444
4519
  Boolean(field.filter || field.searchable || field.sortable),
4445
4520
  ...(field.options ? { options: field.options } : {}),
4446
4521
  ...(field.source ? { source: field.source } : {}),
4522
+ ...(field.userCandidates !== undefined ? { userCandidates: field.userCandidates } : {}),
4447
4523
  ...(field.maxLength !== undefined ? { maxLength: field.maxLength } : {}),
4448
4524
  ...(field.precision !== undefined ? { precision: field.precision } : {}),
4449
4525
  ...(field.scale !== undefined ? { scale: field.scale } : {}),
@@ -4525,6 +4601,7 @@ export function materializeDataResource(appCode, declaration) {
4525
4601
  ...(field.file?.accept ? { accept: field.file.accept } : {}),
4526
4602
  ...(field.options ? { options: field.options } : {}),
4527
4603
  ...(field.source ? { source: field.source } : {}),
4604
+ ...(field.userCandidates !== undefined ? { userCandidates: field.userCandidates } : {}),
4528
4605
  ...(field.timePrecision ? { timePrecision: field.timePrecision } : {}),
4529
4606
  ...(field.serial ? { serial: field.serial } : {}),
4530
4607
  ...(field.subtable ? { subtable: field.subtable } : {}),
@@ -4572,6 +4649,24 @@ export function materializeDataResource(appCode, declaration) {
4572
4649
  ...(declaration.invariants ? { invariants: declaration.invariants } : {}),
4573
4650
  ...(declaration.decimalReservationLifecycle !== undefined ? { decimalReservationLifecycle: declaration.decimalReservationLifecycle } : {}),
4574
4651
  ...(uniqueKeys !== undefined ? { uniqueKeys } : {}),
4652
+ ...(declaration.recordComments === undefined ? {} : { recordComments: {
4653
+ read: declaration.recordComments.read === false ? [] : declaration.recordComments.read === true
4654
+ ? [capabilities.read] : [...declaration.recordComments.read],
4655
+ create: declaration.recordComments.create === false ? [] : declaration.recordComments.create === true
4656
+ ? [capabilities.read] : [...declaration.recordComments.create],
4657
+ } }),
4658
+ ...(declaration.recordPrint === undefined ? {} : { recordPrint: {
4659
+ read: declaration.recordPrint.read === false ? [] : declaration.recordPrint.read === true
4660
+ ? [capabilities.read] : [...declaration.recordPrint.read],
4661
+ } }),
4662
+ ...(declaration.recordDeletion === undefined ? {} : { recordDeletion: {
4663
+ delete: declaration.recordDeletion.delete === false ? [] : declaration.recordDeletion.delete === true
4664
+ ? [capabilities.delete] : [...declaration.recordDeletion.delete],
4665
+ } }),
4666
+ ...(declaration.workflowHistory === undefined ? {} : { workflowHistory: {
4667
+ read: declaration.workflowHistory.read === false ? [] : declaration.workflowHistory.read === true
4668
+ ? [capabilities.read] : [...declaration.workflowHistory.read],
4669
+ } }),
4575
4670
  surface,
4576
4671
  capabilities,
4577
4672
  ...(declaration.dataPolicyCode
@@ -4621,6 +4716,10 @@ export function validateAppDeclaration(value) {
4621
4716
  'detailRouteCode',
4622
4717
  'userSurface',
4623
4718
  'audit',
4719
+ 'workflowHistory',
4720
+ 'recordPrint',
4721
+ 'recordComments',
4722
+ 'recordDeletion',
4624
4723
  ]);
4625
4724
  const fieldKeys = new Set([
4626
4725
  'code',
@@ -4630,6 +4729,7 @@ export function validateAppDeclaration(value) {
4630
4729
  'indexed',
4631
4730
  'options',
4632
4731
  'source',
4732
+ 'userCandidates',
4633
4733
  'maxLength',
4634
4734
  'precision',
4635
4735
  'scale',
@@ -4672,6 +4772,55 @@ export function validateAppDeclaration(value) {
4672
4772
  diagnostics.push(diagnostic('APP_CONFIG_DATA_AUDIT_READ_INVALID', 'audit.read 必须为 true(绑定资源读能力)、false 或不重复的非空 capability 数组(最多 20 项)', `${resourcePath}.audit`, `audit: { read: true } 会把审计读取绑定到本资源的 read 能力;需要更细粒度时写能力数组,例如 audit: { read: ['app:${appCode}:data:<resource>:read'] }`));
4673
4773
  }
4674
4774
  }
4775
+ let recordCommentsValid = true;
4776
+ if (resource.recordComments !== undefined) {
4777
+ const rule = object(resource.recordComments);
4778
+ if (!resource.recordComments || typeof resource.recordComments !== 'object' || Array.isArray(resource.recordComments) ||
4779
+ Object.keys(rule).some(key => !['read', 'create'].includes(key)) || ['read', 'create'].some(mode => {
4780
+ const list = rule[mode];
4781
+ return list !== false && list !== true && (!Array.isArray(list) || list.length < 1 || list.length > 20 ||
4782
+ list.some(value => typeof value !== 'string' || !value.trim()) || new Set(list).size !== list.length);
4783
+ })) {
4784
+ recordCommentsValid = false;
4785
+ diagnostics.push(diagnostic('APP_CONFIG_RECORD_COMMENTS_POLICY_INVALID', 'recordComments.read/create 必须为 true、false 或不重复的非空 capability 数组(最多20项)', `${resourcePath}.recordComments`, 'true绑定本资源read;每项操作的能力需由同一成员全部持有。'));
4786
+ }
4787
+ }
4788
+ let recordDeletionValid = true;
4789
+ if (resource.recordDeletion !== undefined) {
4790
+ const rule = object(resource.recordDeletion);
4791
+ const remove = rule.delete;
4792
+ if (!resource.recordDeletion || typeof resource.recordDeletion !== 'object' || Array.isArray(resource.recordDeletion) ||
4793
+ Object.keys(rule).some(key => key !== 'delete') ||
4794
+ (remove !== true && remove !== false && (!Array.isArray(remove) || remove.length < 1 || remove.length > 20 ||
4795
+ remove.some(value => typeof value !== 'string' || !value.trim()) || new Set(remove).size !== remove.length))) {
4796
+ recordDeletionValid = false;
4797
+ diagnostics.push(diagnostic('APP_CONFIG_RECORD_DELETION_DELETE_INVALID', 'recordDeletion.delete 必须为 true、false 或不重复的非空 capability 数组(最多20项)', `${resourcePath}.recordDeletion`, 'true绑定本资源delete;同一成员需同时有资料read、delete及全部维护能力。'));
4798
+ }
4799
+ }
4800
+ let recordPrintValid = true;
4801
+ if (resource.recordPrint !== undefined) {
4802
+ const rule = object(resource.recordPrint);
4803
+ const read = rule.read;
4804
+ if (!resource.recordPrint || typeof resource.recordPrint !== 'object' || Array.isArray(resource.recordPrint) ||
4805
+ Object.keys(rule).some(key => key !== 'read') ||
4806
+ (read !== true && read !== false && (!Array.isArray(read) || read.length < 1 || read.length > 20 ||
4807
+ read.some(value => typeof value !== 'string' || !value.trim()) || new Set(read).size !== read.length))) {
4808
+ recordPrintValid = false;
4809
+ diagnostics.push(diagnostic('APP_CONFIG_RECORD_PRINT_READ_INVALID', 'recordPrint.read 必须为 true、false 或不重复的非空 capability 数组(最多 20 项)', `${resourcePath}.recordPrint`, 'true 绑定本资源 read;独立能力数组按同一成员全部持有校验。'));
4810
+ }
4811
+ }
4812
+ let workflowHistoryValid = true;
4813
+ if (resource.workflowHistory !== undefined) {
4814
+ const rule = object(resource.workflowHistory);
4815
+ const read = rule.read;
4816
+ if (!resource.workflowHistory || typeof resource.workflowHistory !== 'object' || Array.isArray(resource.workflowHistory) ||
4817
+ Object.keys(rule).some(key => key !== 'read') ||
4818
+ (read !== true && read !== false && (!Array.isArray(read) || read.length < 1 || read.length > 20 ||
4819
+ read.some(value => typeof value !== 'string' || !value.trim()) || new Set(read).size !== read.length))) {
4820
+ workflowHistoryValid = false;
4821
+ diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_HISTORY_READ_INVALID', 'workflowHistory.read 必须为 true、false 或不重复的非空 capability 数组(最多 20 项)', `${resourcePath}.workflowHistory`, 'true 绑定本资源 read;独立能力数组按同一成员全部持有校验。'));
4822
+ }
4823
+ }
4675
4824
  const userSurface = resource.userSurface;
4676
4825
  if (userSurface !== undefined) {
4677
4826
  const surface = object(userSurface);
@@ -4708,7 +4857,7 @@ export function validateAppDeclaration(value) {
4708
4857
  return;
4709
4858
  }
4710
4859
  const declaredCodes = new Set();
4711
- let materializable = auditValid;
4860
+ let materializable = auditValid && workflowHistoryValid && recordPrintValid && recordCommentsValid && recordDeletionValid;
4712
4861
  fields.forEach((rawField, fieldIndex) => {
4713
4862
  const field = object(rawField);
4714
4863
  const fieldPath = `${resourcePath}.fields[${fieldIndex}]`;
@@ -4885,8 +5034,8 @@ export function validateAppDeclaration(value) {
4885
5034
  const configured = Number(object(field.subtable).maxRows);
4886
5035
  return total + (Number.isSafeInteger(configured) ? configured : 20);
4887
5036
  }, 0);
4888
- if (aggregateMaxRows > 49) {
4889
- diagnostics.push(diagnostic('APP_CONFIG_DATA_SUBTABLE_AGGREGATE_MAX_ROWS_EXCEEDED', '同一父资源的所有子表 maxRows 总和不能超过 49', `data.resources[${declaration.resourceIndex}].fields`));
5037
+ if (aggregateMaxRows > DATA_SUBTABLE_MAX_TOTAL_ROWS) {
5038
+ diagnostics.push(diagnostic('APP_CONFIG_DATA_SUBTABLE_AGGREGATE_MAX_ROWS_EXCEEDED', `同一父资源的所有子表 maxRows 总和不能超过 ${DATA_SUBTABLE_MAX_TOTAL_ROWS}`, `data.resources[${declaration.resourceIndex}].fields`));
4890
5039
  }
4891
5040
  fields.forEach((rawField, fieldIndex) => {
4892
5041
  const field = object(rawField);