@flusys/nestjs-form-builder 9.1.1 → 9.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -206,6 +206,17 @@ FormBuilderModule.forRoot({
206
206
 
207
207
  ---
208
208
 
209
+ ## 4. Computed Fields
210
+
211
+ `schema.settings.computedFields[]` are calculated on final submission (not drafts) and stored under `data._computed[key]`. Each field has ordered `rules` - `{ condition?, value }`, first match wins, else `defaultValue` - where `value` is an [Expression](../nestjs-shared/README.md#10-expressions) and `condition` an expression condition group. Combinations (regex extract, then join with another field, then upper-case; inline if / else) are just nested expressions.
212
+
213
+ - References: a field id, a field `name` (what templates use, `{{ email }}`), or `computed.<key>` for a computed field defined **earlier**.
214
+ - `valueType` (`number` | `string` | `boolean`) casts the result; a rule that fails at run time falls back to `defaultValue` instead of failing the submission.
215
+ - Saving a form validates its computed fields (`findComputedFieldIssue`): key format and uniqueness, shape, unknown references or functions, argument counts, literal regex patterns. A problem is a 400 with `form.invalid.computed.field` and `{ field, code, ... }`.
216
+ - **Breaking (v9):** the old rule shape (`computation: { type: 'direct' | 'field_reference' | 'arithmetic', config }` with `{ fieldId, comparison }` conditions) is not read or converted. A form saved with it computes only `defaultValue` on submit and is refused on save until its computed fields are rebuilt in the builder.
217
+
218
+ ---
219
+
209
220
  ## License
210
221
 
211
222
  MIT © FLUSYS
@@ -15,6 +15,7 @@ export declare const FORM_MESSAGES: {
15
15
  readonly OTP_EXPIRED: "form.email.otp.expired";
16
16
  readonly OTP_TOO_MANY_ATTEMPTS: "form.email.otp.too.many.attempts";
17
17
  readonly OTP_VERIFIED: "form.email.otp.verified";
18
+ readonly INVALID_COMPUTED_FIELD: "form.invalid.computed.field";
18
19
  };
19
20
  export declare const FORM_RESULT_MESSAGES: {
20
21
  readonly GET_ALL_SUCCESS: "form.result.get.all.success";
package/fesm/432.js CHANGED
@@ -68,7 +68,8 @@ const FORM_MESSAGES = {
68
68
  OTP_INVALID: 'form.email.otp.invalid',
69
69
  OTP_EXPIRED: 'form.email.otp.expired',
70
70
  OTP_TOO_MANY_ATTEMPTS: 'form.email.otp.too.many.attempts',
71
- OTP_VERIFIED: 'form.email.otp.verified'
71
+ OTP_VERIFIED: 'form.email.otp.verified',
72
+ INVALID_COMPUTED_FIELD: 'form.invalid.computed.field'
72
73
  };
73
74
  const FORM_RESULT_MESSAGES = {
74
75
  GET_ALL_SUCCESS: 'form.result.get.all.success',
@@ -1173,16 +1174,16 @@ FormEmailAuthService = _ts_decorate([
1173
1174
  /* import */ var _flusys_nestjs_shared_modules__rspack_import_3 = __webpack_require__(236);
1174
1175
  /* import */ var _nestjs_common__rspack_import_4 = __webpack_require__(157);
1175
1176
  /* import */ var typeorm__rspack_import_5 = __webpack_require__(158);
1176
- /* import */ var _config_index_js__rspack_import_12 = __webpack_require__(769);
1177
- /* import */ var _config_index_js__rspack_import_13 = __webpack_require__(137);
1177
+ /* import */ var _config_index_js__rspack_import_13 = __webpack_require__(769);
1178
+ /* import */ var _config_index_js__rspack_import_14 = __webpack_require__(137);
1178
1179
  /* import */ var _dtos_form_result_dto_js__rspack_import_6 = __webpack_require__(788);
1179
1180
  /* import */ var _entities_index_js__rspack_import_7 = __webpack_require__(814);
1180
- /* import */ var _enums_form_access_type_enum_js__rspack_import_14 = __webpack_require__(376);
1181
- /* import */ var _utils_computed_field_utils_js__rspack_import_15 = __webpack_require__(801);
1182
- /* import */ var _utils_permission_utils_js__rspack_import_8 = __webpack_require__(988);
1183
- /* import */ var _form_builder_config_service_js__rspack_import_9 = __webpack_require__(781);
1184
- /* import */ var _form_builder_datasource_provider_js__rspack_import_10 = __webpack_require__(593);
1185
- /* import */ var _form_email_auth_service_js__rspack_import_11 = __webpack_require__(474);
1181
+ /* import */ var _enums_form_access_type_enum_js__rspack_import_15 = __webpack_require__(376);
1182
+ /* import */ var _utils_computed_field_utils_js__rspack_import_8 = __webpack_require__(801);
1183
+ /* import */ var _utils_permission_utils_js__rspack_import_9 = __webpack_require__(988);
1184
+ /* import */ var _form_builder_config_service_js__rspack_import_10 = __webpack_require__(781);
1185
+ /* import */ var _form_builder_datasource_provider_js__rspack_import_11 = __webpack_require__(593);
1186
+ /* import */ var _form_email_auth_service_js__rspack_import_12 = __webpack_require__(474);
1186
1187
  function _ts_decorate(decorators, target, key, desc) {
1187
1188
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1188
1189
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -1237,7 +1238,7 @@ class FormResultService extends _flusys_nestjs_shared_classes__rspack_import_0.A
1237
1238
  // Does not span multiple app instances; see withSubmissionLock().
1238
1239
  static submissionLocks = new Map();
1239
1240
  constructor(cacheManager, utilsService, formBuilderConfig, dataSourceProvider, formEmailAuthService, permissionResolver, permissionCache){
1240
- super(_config_index_js__rspack_import_12/* .FORM_BUILDER_EVENT_ENTITIES.FORM_RESULT */.eF.FORM_RESULT, cacheManager, utilsService, FormResultService.name, true, _config_index_js__rspack_import_12/* .FORM_BUILDER_EVENT_MODULE */.T$, dataSourceProvider, _entities_index_js__rspack_import_7/* .FormResult */.ez), this.cacheManager = cacheManager, this.utilsService = utilsService, this.formBuilderConfig = formBuilderConfig, this.dataSourceProvider = dataSourceProvider, this.formEmailAuthService = formEmailAuthService, this.permissionResolver = permissionResolver, this.permissionCache = permissionCache;
1241
+ super(_config_index_js__rspack_import_13/* .FORM_BUILDER_EVENT_ENTITIES.FORM_RESULT */.eF.FORM_RESULT, cacheManager, utilsService, FormResultService.name, true, _config_index_js__rspack_import_13/* .FORM_BUILDER_EVENT_MODULE */.T$, dataSourceProvider, _entities_index_js__rspack_import_7/* .FormResult */.ez), this.cacheManager = cacheManager, this.utilsService = utilsService, this.formBuilderConfig = formBuilderConfig, this.dataSourceProvider = dataSourceProvider, this.formEmailAuthService = formEmailAuthService, this.permissionResolver = permissionResolver, this.permissionCache = permissionCache;
1241
1242
  }
1242
1243
  async ensureFormRepositoryInitialized() {
1243
1244
  if (!this.formRepository) {
@@ -1275,7 +1276,7 @@ class FormResultService extends _flusys_nestjs_shared_classes__rspack_import_0.A
1275
1276
  if (!form) {
1276
1277
  throw new _nestjs_common__rspack_import_4.NotFoundException({
1277
1278
  message: 'Form not found or inactive',
1278
- messageKey: _config_index_js__rspack_import_13/* .FORM_MESSAGES.NOT_FOUND */.Q.NOT_FOUND
1279
+ messageKey: _config_index_js__rspack_import_14/* .FORM_MESSAGES.NOT_FOUND */.Q.NOT_FOUND
1279
1280
  });
1280
1281
  }
1281
1282
  return form;
@@ -1288,67 +1289,67 @@ class FormResultService extends _flusys_nestjs_shared_classes__rspack_import_0.A
1288
1289
  }
1289
1290
  }
1290
1291
  static PUBLICLY_SUBMITTABLE_ACCESS_TYPES = new Set([
1291
- _enums_form_access_type_enum_js__rspack_import_14/* .FormAccessType.PUBLIC */.h.PUBLIC,
1292
- _enums_form_access_type_enum_js__rspack_import_14/* .FormAccessType.EMAIL_VERIFIED */.h.EMAIL_VERIFIED
1292
+ _enums_form_access_type_enum_js__rspack_import_15/* .FormAccessType.PUBLIC */.h.PUBLIC,
1293
+ _enums_form_access_type_enum_js__rspack_import_15/* .FormAccessType.EMAIL_VERIFIED */.h.EMAIL_VERIFIED
1293
1294
  ]);
1294
1295
  async validateSubmissionAccess(form, user, isPublic) {
1295
1296
  if (isPublic) {
1296
1297
  if (!FormResultService.PUBLICLY_SUBMITTABLE_ACCESS_TYPES.has(form.accessType)) {
1297
1298
  throw new _nestjs_common__rspack_import_4.UnauthorizedException({
1298
1299
  message: 'This form is not available for public submission',
1299
- messageKey: _config_index_js__rspack_import_13/* .FORM_MESSAGES.NOT_PUBLIC */.Q.NOT_PUBLIC
1300
+ messageKey: _config_index_js__rspack_import_14/* .FORM_MESSAGES.NOT_PUBLIC */.Q.NOT_PUBLIC
1300
1301
  });
1301
1302
  }
1302
1303
  return;
1303
1304
  }
1304
1305
  switch(form.accessType){
1305
- case _enums_form_access_type_enum_js__rspack_import_14/* .FormAccessType.PUBLIC */.h.PUBLIC:
1306
+ case _enums_form_access_type_enum_js__rspack_import_15/* .FormAccessType.PUBLIC */.h.PUBLIC:
1306
1307
  return;
1307
- case _enums_form_access_type_enum_js__rspack_import_14/* .FormAccessType.EMAIL_VERIFIED */.h.EMAIL_VERIFIED:
1308
+ case _enums_form_access_type_enum_js__rspack_import_15/* .FormAccessType.EMAIL_VERIFIED */.h.EMAIL_VERIFIED:
1308
1309
  throw new _nestjs_common__rspack_import_4.BadRequestException({
1309
1310
  message: 'Email-verified forms must be submitted through the public endpoint',
1310
- messageKey: _config_index_js__rspack_import_13/* .FORM_MESSAGES.INVALID_ACCESS_TYPE */.Q.INVALID_ACCESS_TYPE
1311
+ messageKey: _config_index_js__rspack_import_14/* .FORM_MESSAGES.INVALID_ACCESS_TYPE */.Q.INVALID_ACCESS_TYPE
1311
1312
  });
1312
- case _enums_form_access_type_enum_js__rspack_import_14/* .FormAccessType.ACTION_GROUP */.h.ACTION_GROUP:
1313
- case _enums_form_access_type_enum_js__rspack_import_14/* .FormAccessType.AUTHENTICATED */.h.AUTHENTICATED:
1313
+ case _enums_form_access_type_enum_js__rspack_import_15/* .FormAccessType.ACTION_GROUP */.h.ACTION_GROUP:
1314
+ case _enums_form_access_type_enum_js__rspack_import_15/* .FormAccessType.AUTHENTICATED */.h.AUTHENTICATED:
1314
1315
  break;
1315
1316
  default:
1316
1317
  throw new _nestjs_common__rspack_import_4.BadRequestException({
1317
1318
  message: 'Invalid form access type',
1318
- messageKey: _config_index_js__rspack_import_13/* .FORM_MESSAGES.INVALID_ACCESS_TYPE */.Q.INVALID_ACCESS_TYPE
1319
+ messageKey: _config_index_js__rspack_import_14/* .FORM_MESSAGES.INVALID_ACCESS_TYPE */.Q.INVALID_ACCESS_TYPE
1319
1320
  });
1320
1321
  }
1321
1322
  if (!user) {
1322
1323
  throw new _nestjs_common__rspack_import_4.UnauthorizedException({
1323
1324
  message: 'Authentication required to submit this form',
1324
- messageKey: _config_index_js__rspack_import_13/* .FORM_MESSAGES.AUTH_REQUIRED */.Q.AUTH_REQUIRED
1325
+ messageKey: _config_index_js__rspack_import_14/* .FORM_MESSAGES.AUTH_REQUIRED */.Q.AUTH_REQUIRED
1325
1326
  });
1326
1327
  }
1327
- if (form.accessType === _enums_form_access_type_enum_js__rspack_import_14/* .FormAccessType.ACTION_GROUP */.h.ACTION_GROUP && form.permissionLogic) {
1328
- const permitted = await (0,_utils_permission_utils_js__rspack_import_8/* .hasFormPermission */.E)(user, form.permissionLogic, {
1328
+ if (form.accessType === _enums_form_access_type_enum_js__rspack_import_15/* .FormAccessType.ACTION_GROUP */.h.ACTION_GROUP && form.permissionLogic) {
1329
+ const permitted = await (0,_utils_permission_utils_js__rspack_import_9/* .hasFormPermission */.E)(user, form.permissionLogic, {
1329
1330
  resolver: this.permissionResolver,
1330
1331
  cache: this.permissionCache
1331
1332
  });
1332
1333
  if (!permitted) {
1333
1334
  throw new _nestjs_common__rspack_import_4.ForbiddenException({
1334
1335
  message: 'You do not have permission to submit this form',
1335
- messageKey: _config_index_js__rspack_import_13/* .FORM_MESSAGES.ACCESS_DENIED */.Q.ACCESS_DENIED
1336
+ messageKey: _config_index_js__rspack_import_14/* .FORM_MESSAGES.ACCESS_DENIED */.Q.ACCESS_DENIED
1336
1337
  });
1337
1338
  }
1338
- } else if (form.accessType !== _enums_form_access_type_enum_js__rspack_import_14/* .FormAccessType.AUTHENTICATED */.h.AUTHENTICATED) {
1339
+ } else if (form.accessType !== _enums_form_access_type_enum_js__rspack_import_15/* .FormAccessType.AUTHENTICATED */.h.AUTHENTICATED) {
1339
1340
  throw new _nestjs_common__rspack_import_4.BadRequestException({
1340
1341
  message: 'Invalid form access type',
1341
- messageKey: _config_index_js__rspack_import_13/* .FORM_MESSAGES.INVALID_ACCESS_TYPE */.Q.INVALID_ACCESS_TYPE
1342
+ messageKey: _config_index_js__rspack_import_14/* .FORM_MESSAGES.INVALID_ACCESS_TYPE */.Q.INVALID_ACCESS_TYPE
1342
1343
  });
1343
1344
  }
1344
1345
  }
1345
1346
  async resolveEmailVerifiedSubmitter(form, user, accessToken) {
1346
- if (form.accessType !== _enums_form_access_type_enum_js__rspack_import_14/* .FormAccessType.EMAIL_VERIFIED */.h.EMAIL_VERIFIED) return null;
1347
+ if (form.accessType !== _enums_form_access_type_enum_js__rspack_import_15/* .FormAccessType.EMAIL_VERIFIED */.h.EMAIL_VERIFIED) return null;
1347
1348
  const email = await this.formEmailAuthService.resolveSubmitterEmail(form.id, user ?? undefined, accessToken);
1348
1349
  if (!email) {
1349
1350
  throw new _nestjs_common__rspack_import_4.UnauthorizedException({
1350
1351
  message: 'Email verification required to submit this form',
1351
- messageKey: _config_index_js__rspack_import_13/* .FORM_MESSAGES.EMAIL_VERIFICATION_REQUIRED */.Q.EMAIL_VERIFICATION_REQUIRED
1352
+ messageKey: _config_index_js__rspack_import_14/* .FORM_MESSAGES.EMAIL_VERIFICATION_REQUIRED */.Q.EMAIL_VERIFICATION_REQUIRED
1352
1353
  });
1353
1354
  }
1354
1355
  return email;
@@ -1368,7 +1369,7 @@ class FormResultService extends _flusys_nestjs_shared_classes__rspack_import_0.A
1368
1369
  if (count > 0) {
1369
1370
  throw new _nestjs_common__rspack_import_4.ConflictException({
1370
1371
  message: 'You have already submitted a response to this form',
1371
- messageKey: _config_index_js__rspack_import_13/* .FORM_RESULT_MESSAGES.ALREADY_SUBMITTED */.y.ALREADY_SUBMITTED
1372
+ messageKey: _config_index_js__rspack_import_14/* .FORM_RESULT_MESSAGES.ALREADY_SUBMITTED */.y.ALREADY_SUBMITTED
1372
1373
  });
1373
1374
  }
1374
1375
  }
@@ -1413,13 +1414,10 @@ class FormResultService extends _flusys_nestjs_shared_classes__rspack_import_0.A
1413
1414
  if (isDraft) {
1414
1415
  return data;
1415
1416
  }
1416
- const schema = form.schema;
1417
- const settings = schema?.settings;
1418
- const computedFields = settings?.computedFields;
1419
- if (!computedFields || computedFields.length === 0) {
1417
+ const computedValues = (0,_utils_computed_field_utils_js__rspack_import_8/* .calculateComputedFields */.XZ)(data, form.schema);
1418
+ if (Object.keys(computedValues).length === 0) {
1420
1419
  return data;
1421
1420
  }
1422
- const computedValues = (0,_utils_computed_field_utils_js__rspack_import_15/* .calculateComputedFields */.X)(data, computedFields);
1423
1421
  return {
1424
1422
  ...data,
1425
1423
  _computed: computedValues
@@ -1528,7 +1526,7 @@ class FormResultService extends _flusys_nestjs_shared_classes__rspack_import_0.A
1528
1526
  if (!draft) {
1529
1527
  throw new _nestjs_common__rspack_import_4.NotFoundException({
1530
1528
  message: 'Draft not found',
1531
- messageKey: _config_index_js__rspack_import_13/* .FORM_RESULT_MESSAGES.NOT_FOUND */.y.NOT_FOUND
1529
+ messageKey: _config_index_js__rspack_import_14/* .FORM_RESULT_MESSAGES.NOT_FOUND */.y.NOT_FOUND
1532
1530
  });
1533
1531
  }
1534
1532
  const form = await this.getActiveForm(dto.formId);
@@ -1548,7 +1546,7 @@ class FormResultService extends _flusys_nestjs_shared_classes__rspack_import_0.A
1548
1546
  return this.convertEntityToResponseDto(saved, false);
1549
1547
  }
1550
1548
  async publishSubmissionEvent(result, formId, isDraft, user) {
1551
- await this.publishDomainAction(isDraft ? _config_index_js__rspack_import_12/* .FORM_BUILDER_EVENT_ACTIONS.DRAFT_SAVED */.ov.DRAFT_SAVED : _config_index_js__rspack_import_12/* .FORM_BUILDER_EVENT_ACTIONS.SUBMITTED */.ov.SUBMITTED, {
1549
+ await this.publishDomainAction(isDraft ? _config_index_js__rspack_import_13/* .FORM_BUILDER_EVENT_ACTIONS.DRAFT_SAVED */.ov.DRAFT_SAVED : _config_index_js__rspack_import_13/* .FORM_BUILDER_EVENT_ACTIONS.SUBMITTED */.ov.SUBMITTED, {
1552
1550
  ids: [
1553
1551
  result.id
1554
1552
  ],
@@ -1641,9 +1639,9 @@ FormResultService = _ts_decorate([
1641
1639
  }),
1642
1640
  _ts_param(0, (0,_nestjs_common__rspack_import_4.Inject)('CACHE_INSTANCE')),
1643
1641
  _ts_param(1, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_modules__rspack_import_3.UtilsService)),
1644
- _ts_param(2, (0,_nestjs_common__rspack_import_4.Inject)(_form_builder_config_service_js__rspack_import_9/* .FormBuilderConfigService */.j)),
1645
- _ts_param(3, (0,_nestjs_common__rspack_import_4.Inject)(_form_builder_datasource_provider_js__rspack_import_10/* .FormBuilderDataSourceProvider */._)),
1646
- _ts_param(4, (0,_nestjs_common__rspack_import_4.Inject)(_form_email_auth_service_js__rspack_import_11/* .FormEmailAuthService */.w)),
1642
+ _ts_param(2, (0,_nestjs_common__rspack_import_4.Inject)(_form_builder_config_service_js__rspack_import_10/* .FormBuilderConfigService */.j)),
1643
+ _ts_param(3, (0,_nestjs_common__rspack_import_4.Inject)(_form_builder_datasource_provider_js__rspack_import_11/* .FormBuilderDataSourceProvider */._)),
1644
+ _ts_param(4, (0,_nestjs_common__rspack_import_4.Inject)(_form_email_auth_service_js__rspack_import_12/* .FormEmailAuthService */.w)),
1647
1645
  _ts_param(5, (0,_nestjs_common__rspack_import_4.Optional)()),
1648
1646
  _ts_param(5, (0,_nestjs_common__rspack_import_4.Inject)(_flusys_nestjs_shared_interfaces__rspack_import_2.PERMISSION_RESOLVER)),
1649
1647
  _ts_param(6, (0,_nestjs_common__rspack_import_4.Optional)()),
@@ -1652,9 +1650,9 @@ FormResultService = _ts_decorate([
1652
1650
  _ts_metadata("design:paramtypes", [
1653
1651
  typeof _flusys_nestjs_shared_classes__rspack_import_0.HybridCache === "undefined" ? Object : _flusys_nestjs_shared_classes__rspack_import_0.HybridCache,
1654
1652
  typeof _flusys_nestjs_shared_modules__rspack_import_3.UtilsService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_3.UtilsService,
1655
- typeof _form_builder_config_service_js__rspack_import_9/* .FormBuilderConfigService */.j === "undefined" ? Object : _form_builder_config_service_js__rspack_import_9/* .FormBuilderConfigService */.j,
1656
- typeof _form_builder_datasource_provider_js__rspack_import_10/* .FormBuilderDataSourceProvider */._ === "undefined" ? Object : _form_builder_datasource_provider_js__rspack_import_10/* .FormBuilderDataSourceProvider */._,
1657
- typeof _form_email_auth_service_js__rspack_import_11/* .FormEmailAuthService */.w === "undefined" ? Object : _form_email_auth_service_js__rspack_import_11/* .FormEmailAuthService */.w,
1653
+ typeof _form_builder_config_service_js__rspack_import_10/* .FormBuilderConfigService */.j === "undefined" ? Object : _form_builder_config_service_js__rspack_import_10/* .FormBuilderConfigService */.j,
1654
+ typeof _form_builder_datasource_provider_js__rspack_import_11/* .FormBuilderDataSourceProvider */._ === "undefined" ? Object : _form_builder_datasource_provider_js__rspack_import_11/* .FormBuilderDataSourceProvider */._,
1655
+ typeof _form_email_auth_service_js__rspack_import_12/* .FormEmailAuthService */.w === "undefined" ? Object : _form_email_auth_service_js__rspack_import_12/* .FormEmailAuthService */.w,
1658
1656
  typeof IPermissionResolver === "undefined" ? Object : IPermissionResolver,
1659
1657
  typeof _flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService === "undefined" ? Object : _flusys_nestjs_shared_modules__rspack_import_3.SharedPermissionCacheService
1660
1658
  ])
@@ -1695,6 +1693,8 @@ var message_keys = __webpack_require__(137);
1695
1693
  var entities = __webpack_require__(814);
1696
1694
  // EXTERNAL MODULE: ./projects/nestjs-form-builder/src/enums/form-access-type.enum.ts
1697
1695
  var form_access_type_enum = __webpack_require__(376);
1696
+ // EXTERNAL MODULE: ./projects/nestjs-form-builder/src/utils/computed-field.utils.ts
1697
+ var computed_field_utils = __webpack_require__(801);
1698
1698
  // EXTERNAL MODULE: ./projects/nestjs-form-builder/src/utils/permission.utils.ts
1699
1699
  var permission_utils = __webpack_require__(988);
1700
1700
  // EXTERNAL MODULE: ./projects/nestjs-form-builder/src/services/form-builder-config.service.ts
@@ -1738,6 +1738,7 @@ function _ts_param(paramIndex, decorator) {
1738
1738
 
1739
1739
 
1740
1740
 
1741
+
1741
1742
  class FormService extends classes_.ApiService {
1742
1743
  cacheManager;
1743
1744
  utilsService;
@@ -1751,6 +1752,9 @@ class FormService extends classes_.ApiService {
1751
1752
  async convertSingleDtoToEntity(dto, user) {
1752
1753
  let form = {};
1753
1754
  let isUpdate = false;
1755
+ if (dto.schema) {
1756
+ this.assertValidComputedFields(dto.schema);
1757
+ }
1754
1758
  // NOTE: Using 'id' in dto check instead of instanceof - instanceof may not work after esbuild bundling
1755
1759
  if ('id' in dto && dto.id && typeof dto.id === 'string') {
1756
1760
  const dbData = await this.repository.findOne({
@@ -1795,6 +1799,19 @@ class FormService extends classes_.ApiService {
1795
1799
  }
1796
1800
  return form;
1797
1801
  }
1802
+ assertValidComputedFields(schema) {
1803
+ const issue = (0,computed_field_utils/* .findComputedFieldIssue */.Qc)(schema);
1804
+ if (!issue) return;
1805
+ throw new common_.BadRequestException({
1806
+ message: `Computed field "${issue.field}" is invalid (${issue.code}).`,
1807
+ messageKey: message_keys/* .FORM_MESSAGES.INVALID_COMPUTED_FIELD */.Q.INVALID_COMPUTED_FIELD,
1808
+ messageVariables: {
1809
+ field: issue.field,
1810
+ code: issue.code,
1811
+ ...issue.variables
1812
+ }
1813
+ });
1814
+ }
1798
1815
  async getSelectQuery(query, _user, select) {
1799
1816
  if (!select || !select.length) {
1800
1817
  select = [
@@ -2074,204 +2091,144 @@ FormService = _ts_decorate([
2074
2091
 
2075
2092
  },
2076
2093
  801(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
2077
- /**
2078
- * Computed Field Calculation Utilities
2079
- *
2080
- * This module provides functions to calculate computed field values
2081
- * based on form submission data and computed field definitions.
2082
- *
2083
- * NOTE: Interface definitions here mirror @flusys/ng-form-builder interfaces
2084
- * but are defined separately to avoid cross-package dependencies.
2085
- * Both use compatible JSON structures for form schema serialization.
2086
- *
2087
- * @see FLUSYS_NG/projects/ng-form-builder/interfaces/computed-field.interface.ts
2088
- */ // ============================================================================
2089
- // Type Definitions
2090
- // ============================================================================
2091
- // ============================================================================
2092
- // Type Guards & Helpers
2093
- // ============================================================================
2094
- function isObject(value) {
2095
- return typeof value === 'object' && value !== null;
2096
- }
2097
- function isDirectValueConfig(config) {
2098
- return isObject(config) && config.type === 'direct' && 'value' in config;
2094
+ /* import */ var _flusys_nestjs_shared_utils__rspack_import_0 = __webpack_require__(216);
2095
+
2096
+ /** A field reference to an earlier computed field: `computed.<key>`. */ const COMPUTED_REF_PREFIX = 'computed.';
2097
+ const COMPUTED_KEY_PATTERN = /^[A-Za-z_]\w*$/;
2098
+ function asSchema(schema) {
2099
+ return typeof schema === 'object' && schema !== null ? schema : {};
2099
2100
  }
2100
- function isFieldReferenceConfig(config) {
2101
- return isObject(config) && config.type === 'field_reference' && 'fieldId' in config;
2101
+ function formFields(schema) {
2102
+ return (schema.sections ?? []).flatMap((section)=>section.fields ?? []).filter((field)=>typeof field?.id === 'string');
2102
2103
  }
2103
- function isArithmeticConfig(config) {
2104
- return isObject(config) && config.type === 'arithmetic' && 'operation' in config && 'operands' in config && Array.isArray(config.operands);
2104
+ /** References resolve by field id, by field name (what templates use), or `computed.<key>`. */ function createResolver(data, fields, computed) {
2105
+ const idByName = new Map(fields.filter((f)=>f.name).map((f)=>[
2106
+ f.name,
2107
+ f.id
2108
+ ]));
2109
+ return (ref)=>{
2110
+ if (ref.startsWith(COMPUTED_REF_PREFIX)) {
2111
+ const key = ref.slice(COMPUTED_REF_PREFIX.length);
2112
+ return Object.hasOwn(computed, key) ? computed[key] : undefined;
2113
+ }
2114
+ if (Object.hasOwn(data, ref)) return data[ref];
2115
+ const id = idByName.get(ref);
2116
+ return id !== undefined && Object.hasOwn(data, id) ? data[id] : undefined;
2117
+ };
2105
2118
  }
2106
- function isEmptyValue(value) {
2107
- return value === null || value === undefined || value === '' || Array.isArray(value) && value.length === 0;
2119
+ function castValue(value, valueType) {
2120
+ if (value === null || value === undefined) return null;
2121
+ switch(valueType){
2122
+ case 'number':
2123
+ try {
2124
+ return (0,_flusys_nestjs_shared_utils__rspack_import_0.expressionToNumber)(value);
2125
+ } catch {
2126
+ return null;
2127
+ }
2128
+ case 'boolean':
2129
+ if (typeof value === 'boolean') return value;
2130
+ return [
2131
+ 'true',
2132
+ '1',
2133
+ 'yes'
2134
+ ].includes(((0,_flusys_nestjs_shared_utils__rspack_import_0.expressionToText)(value) ?? '').trim().toLowerCase());
2135
+ default:
2136
+ return (0,_flusys_nestjs_shared_utils__rspack_import_0.expressionToText)(value);
2137
+ }
2108
2138
  }
2109
- /**
2110
- * Normalize operator to uppercase string (handles both enum and string values)
2111
- */ function normalizeOperator(op) {
2112
- const strOp = String(op).toUpperCase();
2113
- return strOp === 'OR' ? 'OR' : 'AND';
2139
+ function computeField(field, ctx) {
2140
+ for (const rule of field.rules ?? []){
2141
+ if (!rule.condition || (0,_flusys_nestjs_shared_utils__rspack_import_0.evaluateConditionGroup)(rule.condition, ctx)) return (0,_flusys_nestjs_shared_utils__rspack_import_0.evaluateExpression)(rule.value, ctx);
2142
+ }
2143
+ return field.defaultValue ?? null;
2114
2144
  }
2115
2145
  /**
2116
- * Calculate all computed fields for a form submission
2117
- *
2118
- * @param formData - The submitted form data (field values by field ID)
2119
- * @param computedFields - Array of computed field definitions from form settings
2120
- * @returns Object containing computed field values keyed by their key property
2121
- */ function calculateComputedFields(formData, computedFields) {
2122
- if (!computedFields || computedFields.length === 0) {
2123
- return {};
2124
- }
2146
+ * Computed values for a submission, keyed by each computed field's `key`. Fields run in order,
2147
+ * so a field may use earlier ones (`computed.<key>`); a field whose rules fail at run time gets
2148
+ * its default value instead of failing the submission.
2149
+ */ function calculateComputedFields(data, schema) {
2150
+ const parsed = asSchema(schema);
2151
+ const computedFields = parsed.settings?.computedFields ?? [];
2125
2152
  const result = {};
2153
+ if (computedFields.length === 0) return result;
2154
+ const ctx = {
2155
+ resolveRef: createResolver(data, formFields(parsed), result)
2156
+ };
2126
2157
  for (const field of computedFields){
2158
+ let value;
2127
2159
  try {
2128
- const value = calculateComputedField(formData, field);
2129
- result[field.key] = value;
2130
- } catch (error) {
2131
- // If calculation fails, use default value or null
2132
- result[field.key] = field.defaultValue ?? null;
2160
+ value = computeField(field, ctx);
2161
+ } catch {
2162
+ value = field.defaultValue ?? null;
2133
2163
  }
2164
+ result[field.key] = castValue(value, field.valueType);
2134
2165
  }
2135
2166
  return result;
2136
2167
  }
2137
- /**
2138
- * Calculate a single computed field value
2139
- */ function calculateComputedField(formData, field) {
2140
- // Evaluate rules in order, first matching rule wins
2141
- for (const rule of field.rules){
2142
- if (evaluateCondition(formData, rule.condition)) {
2143
- return executeComputation(formData, rule.computation, field.valueType);
2168
+ /** First problem in the schema's computed fields, or null. Run on save so authors see it there, not at submit. */ function findComputedFieldIssue(schema) {
2169
+ const parsed = asSchema(schema);
2170
+ const computedFields = parsed.settings?.computedFields ?? [];
2171
+ const fields = formFields(parsed);
2172
+ const knownRefs = new Set(fields.flatMap((f)=>f.name ? [
2173
+ f.id,
2174
+ f.name
2175
+ ] : [
2176
+ f.id
2177
+ ]));
2178
+ const earlierKeys = new Set();
2179
+ for (const field of computedFields){
2180
+ const label = String(field?.name || field?.key || '');
2181
+ if (typeof field?.key !== 'string' || !COMPUTED_KEY_PATTERN.test(field.key)) {
2182
+ return {
2183
+ field: label,
2184
+ code: 'invalid_key',
2185
+ variables: {
2186
+ key: String(field?.key ?? '')
2187
+ }
2188
+ };
2144
2189
  }
2145
- }
2146
- // No rule matched, return default
2147
- return field.defaultValue ?? null;
2148
- }
2149
- /**
2150
- * Evaluate a condition group
2151
- */ function evaluateCondition(formData, conditionGroup) {
2152
- // No condition = always true
2153
- if (!conditionGroup || !conditionGroup.conditions || conditionGroup.conditions.length === 0) {
2154
- return true;
2155
- }
2156
- const results = conditionGroup.conditions.map((condition)=>evaluateSingleCondition(formData, condition));
2157
- // Normalize operator to handle both string and enum values
2158
- const operator = normalizeOperator(conditionGroup.operator);
2159
- if (operator === 'AND') {
2160
- return results.every((r)=>r);
2161
- } else {
2162
- return results.some((r)=>r);
2163
- }
2164
- }
2165
- /**
2166
- * Evaluate a single condition
2167
- */ function evaluateSingleCondition(formData, condition) {
2168
- const fieldValue = formData[condition.fieldId];
2169
- const compareValue = condition.value;
2170
- switch(condition.comparison){
2171
- case 'equals':
2172
- return fieldValue === compareValue || String(fieldValue) === String(compareValue);
2173
- case 'not_equals':
2174
- return fieldValue !== compareValue && String(fieldValue) !== String(compareValue);
2175
- case 'is_empty':
2176
- return isEmptyValue(fieldValue);
2177
- case 'is_not_empty':
2178
- return !isEmptyValue(fieldValue);
2179
- case 'contains':
2180
- return String(fieldValue).includes(String(compareValue));
2181
- case 'not_contains':
2182
- return !String(fieldValue).includes(String(compareValue));
2183
- case 'starts_with':
2184
- return String(fieldValue).startsWith(String(compareValue));
2185
- case 'ends_with':
2186
- return String(fieldValue).endsWith(String(compareValue));
2187
- case 'greater_than':
2188
- return Number(fieldValue) > Number(compareValue);
2189
- case 'less_than':
2190
- return Number(fieldValue) < Number(compareValue);
2191
- case 'greater_or_equal':
2192
- return Number(fieldValue) >= Number(compareValue);
2193
- case 'less_or_equal':
2194
- return Number(fieldValue) <= Number(compareValue);
2195
- case 'is_before':
2196
- return new Date(String(fieldValue)) < new Date(String(compareValue));
2197
- case 'is_after':
2198
- return new Date(String(fieldValue)) > new Date(String(compareValue));
2199
- case 'is_checked':
2200
- return fieldValue === true || fieldValue === 'true' || fieldValue === 1;
2201
- case 'is_not_checked':
2202
- return fieldValue === false || fieldValue === 'false' || fieldValue === 0 || !fieldValue;
2203
- case 'is_any_of':
2204
- case 'in':
2205
- return Array.isArray(compareValue) && compareValue.includes(fieldValue);
2206
- case 'is_none_of':
2207
- case 'not_in':
2208
- return !Array.isArray(compareValue) || !compareValue.includes(fieldValue);
2209
- default:
2210
- return false;
2211
- }
2212
- }
2213
- /**
2214
- * Execute a computation and return the result
2215
- */ function executeComputation(formData, computation, valueType) {
2216
- const { config } = computation;
2217
- // Use type guards for safe access
2218
- if (computation.type === 'direct' && isDirectValueConfig(config)) {
2219
- return valueType === 'number' ? Number(config.value) : String(config.value);
2220
- }
2221
- if (computation.type === 'field_reference' && isFieldReferenceConfig(config)) {
2222
- const value = formData[config.fieldId];
2223
- if (value === null || value === undefined) {
2224
- return null;
2190
+ if (earlierKeys.has(field.key)) return {
2191
+ field: label,
2192
+ code: 'duplicate_key',
2193
+ variables: {
2194
+ key: field.key
2195
+ }
2196
+ };
2197
+ if (!Array.isArray(field.rules)) return {
2198
+ field: label,
2199
+ code: 'invalid_node',
2200
+ variables: {
2201
+ type: 'rules'
2202
+ }
2203
+ };
2204
+ const isKnownRef = (ref)=>knownRefs.has(ref) || ref.startsWith(COMPUTED_REF_PREFIX) && earlierKeys.has(ref.slice(COMPUTED_REF_PREFIX.length));
2205
+ try {
2206
+ for (const rule of field.rules){
2207
+ if (rule.condition) (0,_flusys_nestjs_shared_utils__rspack_import_0.assertValidConditionGroup)(rule.condition, {
2208
+ isKnownRef
2209
+ });
2210
+ (0,_flusys_nestjs_shared_utils__rspack_import_0.assertValidExpression)(rule.value, {
2211
+ isKnownRef
2212
+ });
2213
+ }
2214
+ } catch (error) {
2215
+ if ((0,_flusys_nestjs_shared_utils__rspack_import_0.isExpressionError)(error)) return {
2216
+ field: label,
2217
+ code: error.code,
2218
+ variables: error.variables
2219
+ };
2220
+ throw error;
2225
2221
  }
2226
- return valueType === 'number' ? Number(value) : String(value);
2222
+ earlierKeys.add(field.key);
2227
2223
  }
2228
- if (computation.type === 'arithmetic' && isArithmeticConfig(config)) {
2229
- return calculateArithmetic(formData, config);
2230
- }
2231
- // Unknown computation type
2232
2224
  return null;
2233
2225
  }
2234
- /**
2235
- * Perform arithmetic calculation
2236
- */ function calculateArithmetic(formData, config) {
2237
- const values = config.operands.map((operand)=>{
2238
- if (operand.type === 'constant') {
2239
- return operand.value ?? 0;
2240
- } else {
2241
- const fieldValue = formData[operand.fieldId ?? ''];
2242
- return fieldValue !== null && fieldValue !== undefined ? Number(fieldValue) : 0;
2243
- }
2244
- });
2245
- if (values.length === 0) {
2246
- return null;
2247
- }
2248
- switch(config.operation){
2249
- case 'sum':
2250
- case 'increment':
2251
- return values.reduce((acc, val)=>acc + val, 0);
2252
- case 'subtract':
2253
- case 'decrement':
2254
- return values.reduce((acc, val, idx)=>idx === 0 ? val : acc - val, 0);
2255
- case 'multiply':
2256
- return values.reduce((acc, val)=>acc * val, 1);
2257
- case 'divide':
2258
- {
2259
- const [first, ...rest] = values;
2260
- return rest.reduce((acc, val)=>val !== 0 ? acc / val : acc, first);
2261
- }
2262
- case 'average':
2263
- return values.reduce((acc, val)=>acc + val, 0) / values.length;
2264
- case 'min':
2265
- return Math.min(...values);
2266
- case 'max':
2267
- return Math.max(...values);
2268
- default:
2269
- return null;
2270
- }
2271
- }
2272
2226
 
2273
2227
  __webpack_require__.d(__webpack_exports__, {
2274
- X: () => (calculateComputedFields)
2228
+ Qc: () => (findComputedFieldIssue),
2229
+ XZ: () => (calculateComputedFields)
2230
+ }, {
2231
+ xI: COMPUTED_REF_PREFIX
2275
2232
  });
2276
2233
 
2277
2234
 
@@ -71,7 +71,8 @@ const FORM_MESSAGES = {
71
71
  OTP_INVALID: 'form.email.otp.invalid',
72
72
  OTP_EXPIRED: 'form.email.otp.expired',
73
73
  OTP_TOO_MANY_ATTEMPTS: 'form.email.otp.too.many.attempts',
74
- OTP_VERIFIED: 'form.email.otp.verified'
74
+ OTP_VERIFIED: 'form.email.otp.verified',
75
+ INVALID_COMPUTED_FIELD: 'form.invalid.computed.field'
75
76
  };
76
77
  const FORM_RESULT_MESSAGES = {
77
78
  GET_ALL_SUCCESS: 'form.result.get.all.success',
package/fesm/index.js CHANGED
@@ -194,6 +194,7 @@ __webpack_require__.d(__webpack_exports__, {
194
194
 
195
195
  // EXPORTS
196
196
  __webpack_require__.d(__webpack_exports__, {
197
+ xI: () => (/* reexport */ computed_field_utils/* .COMPUTED_REF_PREFIX */.xI),
197
198
  YD: () => (/* reexport */ dtos/* .CreateFormDto */.YD),
198
199
  pZ: () => (/* reexport */ dtos/* .CreateFormResultDto */.pZ),
199
200
  d7: () => (/* reexport */ dtos/* .EmailVerifiedFormResponseDto */.d7),
@@ -236,7 +237,8 @@ __webpack_require__.d(__webpack_exports__, {
236
237
  Uj: () => (/* reexport */ dtos/* .UpdateFormResultDto */.Uj),
237
238
  uY: () => (/* reexport */ dtos/* .VerifyFormEmailOtpDto */.uY),
238
239
  TU: () => (/* reexport */ dtos/* .VerifyOtpResponseDto */.TU),
239
- XZ: () => (/* reexport */ computed_field_utils/* .calculateComputedFields */.X),
240
+ XZ: () => (/* reexport */ computed_field_utils/* .calculateComputedFields */.XZ),
241
+ Qc: () => (/* reexport */ computed_field_utils/* .findComputedFieldIssue */.Qc),
240
242
  aY: () => (/* reexport */ docs/* .formBuilderSwaggerConfig */.a),
241
243
  iO: () => (/* reexport */ entities.getFormBuilderEntitiesByConfig),
242
244
  EY: () => (/* reexport */ permission_utils/* .hasFormPermission */.E)
@@ -488,7 +490,8 @@ import * as __rspack_chunk_2 from './720.js';
488
490
  __webpack_require__.C(__rspack_chunk_2);
489
491
  import * as __rspack_chunk_3 from './index.js';
490
492
  __webpack_require__.C(__rspack_chunk_3);
491
- var __webpack_exports__ = __webpack_exec__(851);var __webpack_exports__CreateFormDto = __webpack_exports__.YD;
493
+ var __webpack_exports__ = __webpack_exec__(851);var __webpack_exports__COMPUTED_REF_PREFIX = __webpack_exports__.xI;
494
+ var __webpack_exports__CreateFormDto = __webpack_exports__.YD;
492
495
  var __webpack_exports__CreateFormResultDto = __webpack_exports__.pZ;
493
496
  var __webpack_exports__EmailVerifiedFormResponseDto = __webpack_exports__.d7;
494
497
  var __webpack_exports__FORM_BUILDER_EMAIL_PROVIDER = __webpack_exports__.Q3;
@@ -531,7 +534,8 @@ var __webpack_exports__UpdateFormResultDto = __webpack_exports__.Uj;
531
534
  var __webpack_exports__VerifyFormEmailOtpDto = __webpack_exports__.uY;
532
535
  var __webpack_exports__VerifyOtpResponseDto = __webpack_exports__.TU;
533
536
  var __webpack_exports__calculateComputedFields = __webpack_exports__.XZ;
537
+ var __webpack_exports__findComputedFieldIssue = __webpack_exports__.Qc;
534
538
  var __webpack_exports__formBuilderSwaggerConfig = __webpack_exports__.aY;
535
539
  var __webpack_exports__getFormBuilderEntitiesByConfig = __webpack_exports__.iO;
536
540
  var __webpack_exports__hasFormPermission = __webpack_exports__.EY;
537
- export { __webpack_exports__CreateFormDto as CreateFormDto, __webpack_exports__CreateFormResultDto as CreateFormResultDto, __webpack_exports__EmailVerifiedFormResponseDto as EmailVerifiedFormResponseDto, __webpack_exports__FORM_BUILDER_EMAIL_PROVIDER as FORM_BUILDER_EMAIL_PROVIDER, __webpack_exports__FORM_BUILDER_EVENT_ACTIONS as FORM_BUILDER_EVENT_ACTIONS, __webpack_exports__FORM_BUILDER_EVENT_ENTITIES as FORM_BUILDER_EVENT_ENTITIES, __webpack_exports__FORM_BUILDER_EVENT_MODULE as FORM_BUILDER_EVENT_MODULE, __webpack_exports__FORM_BUILDER_MODULE_OPTIONS as FORM_BUILDER_MODULE_OPTIONS, __webpack_exports__FORM_MESSAGES as FORM_MESSAGES, __webpack_exports__FORM_RESULT_MESSAGES as FORM_RESULT_MESSAGES, __webpack_exports__Form as Form, __webpack_exports__FormAccessInfoResponseDto as FormAccessInfoResponseDto, __webpack_exports__FormAccessType as FormAccessType, __webpack_exports__FormBase as FormBase, __webpack_exports__FormBuilderConfigService as FormBuilderConfigService, __webpack_exports__FormBuilderDataSourceProvider as FormBuilderDataSourceProvider, __webpack_exports__FormBuilderModule as FormBuilderModule, __webpack_exports__FormCompanyEntities as FormCompanyEntities, __webpack_exports__FormController as FormController, __webpack_exports__FormCoreEntities as FormCoreEntities, __webpack_exports__FormEmailAuthController as FormEmailAuthController, __webpack_exports__FormEmailAuthService as FormEmailAuthService, __webpack_exports__FormEmailVerification as FormEmailVerification, __webpack_exports__FormResponseDto as FormResponseDto, __webpack_exports__FormResult as FormResult, __webpack_exports__FormResultController as FormResultController, __webpack_exports__FormResultResponseDto as FormResultResponseDto, __webpack_exports__FormResultService as FormResultService, __webpack_exports__FormService as FormService, __webpack_exports__FormWithCompany as FormWithCompany, __webpack_exports__GetMyDraftDto as GetMyDraftDto, __webpack_exports__GetResultsByFormDto as GetResultsByFormDto, __webpack_exports__HasSubmittedEmailDto as HasSubmittedEmailDto, __webpack_exports__LoadEmailVerifiedFormDto as LoadEmailVerifiedFormDto, __webpack_exports__PublicFormResponseDto as PublicFormResponseDto, __webpack_exports__RequestFormEmailOtpDto as RequestFormEmailOtpDto, __webpack_exports__SubmitFormDto as SubmitFormDto, __webpack_exports__UpdateDraftDto as UpdateDraftDto, __webpack_exports__UpdateFormDto as UpdateFormDto, __webpack_exports__UpdateFormResultDto as UpdateFormResultDto, __webpack_exports__VerifyFormEmailOtpDto as VerifyFormEmailOtpDto, __webpack_exports__VerifyOtpResponseDto as VerifyOtpResponseDto, __webpack_exports__calculateComputedFields as calculateComputedFields, __webpack_exports__formBuilderSwaggerConfig as formBuilderSwaggerConfig, __webpack_exports__getFormBuilderEntitiesByConfig as getFormBuilderEntitiesByConfig, __webpack_exports__hasFormPermission as hasFormPermission };
541
+ export { __webpack_exports__COMPUTED_REF_PREFIX as COMPUTED_REF_PREFIX, __webpack_exports__CreateFormDto as CreateFormDto, __webpack_exports__CreateFormResultDto as CreateFormResultDto, __webpack_exports__EmailVerifiedFormResponseDto as EmailVerifiedFormResponseDto, __webpack_exports__FORM_BUILDER_EMAIL_PROVIDER as FORM_BUILDER_EMAIL_PROVIDER, __webpack_exports__FORM_BUILDER_EVENT_ACTIONS as FORM_BUILDER_EVENT_ACTIONS, __webpack_exports__FORM_BUILDER_EVENT_ENTITIES as FORM_BUILDER_EVENT_ENTITIES, __webpack_exports__FORM_BUILDER_EVENT_MODULE as FORM_BUILDER_EVENT_MODULE, __webpack_exports__FORM_BUILDER_MODULE_OPTIONS as FORM_BUILDER_MODULE_OPTIONS, __webpack_exports__FORM_MESSAGES as FORM_MESSAGES, __webpack_exports__FORM_RESULT_MESSAGES as FORM_RESULT_MESSAGES, __webpack_exports__Form as Form, __webpack_exports__FormAccessInfoResponseDto as FormAccessInfoResponseDto, __webpack_exports__FormAccessType as FormAccessType, __webpack_exports__FormBase as FormBase, __webpack_exports__FormBuilderConfigService as FormBuilderConfigService, __webpack_exports__FormBuilderDataSourceProvider as FormBuilderDataSourceProvider, __webpack_exports__FormBuilderModule as FormBuilderModule, __webpack_exports__FormCompanyEntities as FormCompanyEntities, __webpack_exports__FormController as FormController, __webpack_exports__FormCoreEntities as FormCoreEntities, __webpack_exports__FormEmailAuthController as FormEmailAuthController, __webpack_exports__FormEmailAuthService as FormEmailAuthService, __webpack_exports__FormEmailVerification as FormEmailVerification, __webpack_exports__FormResponseDto as FormResponseDto, __webpack_exports__FormResult as FormResult, __webpack_exports__FormResultController as FormResultController, __webpack_exports__FormResultResponseDto as FormResultResponseDto, __webpack_exports__FormResultService as FormResultService, __webpack_exports__FormService as FormService, __webpack_exports__FormWithCompany as FormWithCompany, __webpack_exports__GetMyDraftDto as GetMyDraftDto, __webpack_exports__GetResultsByFormDto as GetResultsByFormDto, __webpack_exports__HasSubmittedEmailDto as HasSubmittedEmailDto, __webpack_exports__LoadEmailVerifiedFormDto as LoadEmailVerifiedFormDto, __webpack_exports__PublicFormResponseDto as PublicFormResponseDto, __webpack_exports__RequestFormEmailOtpDto as RequestFormEmailOtpDto, __webpack_exports__SubmitFormDto as SubmitFormDto, __webpack_exports__UpdateDraftDto as UpdateDraftDto, __webpack_exports__UpdateFormDto as UpdateFormDto, __webpack_exports__UpdateFormResultDto as UpdateFormResultDto, __webpack_exports__VerifyFormEmailOtpDto as VerifyFormEmailOtpDto, __webpack_exports__VerifyOtpResponseDto as VerifyOtpResponseDto, __webpack_exports__calculateComputedFields as calculateComputedFields, __webpack_exports__findComputedFieldIssue as findComputedFieldIssue, __webpack_exports__formBuilderSwaggerConfig as formBuilderSwaggerConfig, __webpack_exports__getFormBuilderEntitiesByConfig as getFormBuilderEntitiesByConfig, __webpack_exports__hasFormPermission as hasFormPermission };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flusys/nestjs-form-builder",
3
- "version": "9.1.1",
3
+ "version": "9.1.2",
4
4
  "description": "Dynamic form builder module with schema versioning and access control",
5
5
  "type": "module",
6
6
  "main": "./fesm/index.js",
@@ -75,7 +75,7 @@
75
75
  "express": "^5.0.0"
76
76
  },
77
77
  "dependencies": {
78
- "@flusys/nestjs-core": "9.1.1",
79
- "@flusys/nestjs-shared": "9.1.1"
78
+ "@flusys/nestjs-core": "9.1.2",
79
+ "@flusys/nestjs-shared": "9.1.2"
80
80
  }
81
81
  }
@@ -17,6 +17,7 @@ export declare class FormService extends ApiService<CreateFormDto, UpdateFormDto
17
17
  private readonly permissionCache?;
18
18
  constructor(cacheManager: HybridCache, utilsService: UtilsService, formBuilderConfig: FormBuilderConfigService, dataSourceProvider: FormBuilderDataSourceProvider, permissionResolver?: IPermissionResolver | undefined, permissionCache?: SharedPermissionCacheService | undefined);
19
19
  convertSingleDtoToEntity(dto: CreateFormDto | UpdateFormDto, user: ILoggedUserInfo | null): Promise<Form>;
20
+ private assertValidComputedFields;
20
21
  getSelectQuery(query: SelectQueryBuilder<Form>, _user: ILoggedUserInfo | null, select?: string[]): Promise<{
21
22
  query: SelectQueryBuilder<Form>;
22
23
  isRaw: boolean;
@@ -1,48 +1,24 @@
1
+ import type { Expression, IExpressionConditionGroup } from '@flusys/nestjs-shared/interfaces';
2
+ export type ComputedValueType = 'string' | 'number' | 'boolean';
1
3
  export interface IComputedField {
2
4
  id: string;
3
5
  name: string;
4
6
  key: string;
5
- valueType: 'string' | 'number';
7
+ valueType: ComputedValueType;
6
8
  rules: IComputedRule[];
7
- defaultValue?: string | number | null;
9
+ defaultValue?: string | number | boolean | null;
8
10
  description?: string;
9
11
  }
10
12
  export interface IComputedRule {
11
13
  id: string;
12
- condition?: IComputedConditionGroup;
13
- computation: IComputation;
14
+ condition?: IExpressionConditionGroup;
15
+ value: Expression;
14
16
  }
15
- export interface IComputedConditionGroup {
16
- operator: 'AND' | 'OR' | string;
17
- conditions: IComputedCondition[];
17
+ export declare const COMPUTED_REF_PREFIX = "computed.";
18
+ export interface IComputedFieldIssue {
19
+ field: string;
20
+ code: string;
21
+ variables: Record<string, string | number>;
18
22
  }
19
- export interface IComputedCondition {
20
- fieldId: string;
21
- comparison: string;
22
- value: unknown;
23
- }
24
- export interface IComputation {
25
- type: ComputationType;
26
- config: IDirectValueConfig | IFieldReferenceConfig | IArithmeticConfig;
27
- }
28
- export type ComputationType = 'direct' | 'field_reference' | 'arithmetic';
29
- export interface IDirectValueConfig {
30
- type: 'direct';
31
- value: string | number;
32
- }
33
- export interface IFieldReferenceConfig {
34
- type: 'field_reference';
35
- fieldId: string;
36
- }
37
- export interface IArithmeticConfig {
38
- type: 'arithmetic';
39
- operation: ArithmeticOperation;
40
- operands: IArithmeticOperand[];
41
- }
42
- export type ArithmeticOperation = 'sum' | 'subtract' | 'multiply' | 'divide' | 'average' | 'min' | 'max' | 'increment' | 'decrement';
43
- export interface IArithmeticOperand {
44
- type: 'field' | 'constant';
45
- fieldId?: string;
46
- value?: number;
47
- }
48
- export declare function calculateComputedFields(formData: Record<string, unknown>, computedFields: IComputedField[] | undefined): Record<string, unknown>;
23
+ export declare function calculateComputedFields(data: Record<string, unknown>, schema: unknown): Record<string, unknown>;
24
+ export declare function findComputedFieldIssue(schema: unknown): IComputedFieldIssue | null;