@opencrvs/toolkit 1.9.8-rc.34c53bf → 1.9.8-rc.48851c5

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.
@@ -36,6 +36,7 @@ __export(conditionals_exports, {
36
36
  defineConditional: () => defineConditional,
37
37
  defineFormConditional: () => defineFormConditional,
38
38
  flag: () => flag,
39
+ isCodeToEvaluate: () => isCodeToEvaluate,
39
40
  isFieldReference: () => isFieldReference,
40
41
  never: () => never,
41
42
  not: () => not,
@@ -249,6 +250,9 @@ var user = Object.assign(userSerializer, {
249
250
  function isFieldReference(value) {
250
251
  return typeof value === "object" && value !== null && "$$field" in value;
251
252
  }
253
+ function isCodeToEvaluate(value) {
254
+ return typeof value === "object" && value !== null && "$$code" in value;
255
+ }
252
256
  function getDateRangeToFieldReference(field, comparedField, clause) {
253
257
  return {
254
258
  type: "object",
@@ -685,6 +689,112 @@ function createFieldConditionals(fieldId) {
685
689
  required: [fieldId]
686
690
  });
687
691
  },
692
+ /**
693
+ * Executes a custom validation function defined by country configuration.
694
+ *
695
+ * **Use this as a last resort when predefined toolkit methods are insufficient.**
696
+ *
697
+ * The function is serialized via toString() and transmitted as part of the JSON Schema.
698
+ * It is deserialized just-in-time during validation on the client.
699
+ *
700
+ * @param fn - Validation function that receives the field value and form context.
701
+ * Must return true if valid, false if invalid.
702
+ *
703
+ * @returns JSONSchema conditional for AJV validation
704
+ *
705
+ * @example
706
+ * // Simple age validation
707
+ * field('age').customClientValidator((value, ctx) => {
708
+ * return value >= 18
709
+ * })
710
+ *
711
+ * @example
712
+ * // Cross-field validation: child DOB must be after mother DOB
713
+ * field('child.dob').customClientValidator((value, ctx) => {
714
+ * const motherDob = ctx.$form['mother.dob']
715
+ * if (!motherDob || !value) return false
716
+ * return new Date(value) > new Date(motherDob)
717
+ * })
718
+ *
719
+ * @example
720
+ * // Two number fields validated together
721
+ * field('fieldA').customClientValidator((value, ctx) => {
722
+ * const fieldB = ctx.$form['fieldB']
723
+ * if (!fieldB) return false
724
+ * return value + fieldB > 100
725
+ * })
726
+ *
727
+ * @remarks
728
+ * Limitations:
729
+ * - Client-side only. For backend validation, use country config event triggers.
730
+ * - Cannot reference external libraries (lodash, etc.)
731
+ * - Function must be pure — no side effects, no closures over external state
732
+ * - Function must be serialisable via toString()
733
+ * - Receives (value, context) parameters where context contains $form, $now, $online, etc.
734
+ * - Must return boolean: true = valid, false = invalid
735
+ *
736
+ * @see https://github.com/opencrvs/opencrvs-core/issues/11653
737
+ */
738
+ customClientValidator(fn) {
739
+ const serializedFn = fn.toString();
740
+ return defineFormConditional({
741
+ type: "object",
742
+ properties: {
743
+ [fieldId]: wrapToPath(
744
+ {
745
+ customClientValidator: serializedFn
746
+ },
747
+ this.$$subfield
748
+ )
749
+ },
750
+ required: [fieldId]
751
+ });
752
+ },
753
+ /**
754
+ * Defines a client-side computation function for dynamic field values.
755
+ *
756
+ * **Use this as a last resort when predefined toolkit methods are insufficient.**
757
+ *
758
+ * The function is serialized via toString() and transmitted to the client,
759
+ * where it is deserialized and executed to compute field values dynamically.
760
+ *
761
+ * @param fn - Computation function that receives the field value and form context.
762
+ * Returns the computed value (any type).
763
+ *
764
+ * @returns CodeToEvaluate object for client-side execution
765
+ *
766
+ * @example
767
+ * // Concatenate first and last name
768
+ * field('fullName').customClientEvaluation((value, ctx) => {
769
+ * return `${ctx.$form.firstName} ${ctx.$form.lastName}`
770
+ * })
771
+ *
772
+ * @example
773
+ * // Calculate age from date of birth
774
+ * field('childAge').customClientEvaluation((value, ctx) => {
775
+ * const dob = ctx.$form['child.dob']
776
+ * if (!dob) return undefined
777
+ * const age = Math.floor((new Date(ctx.$now) - new Date(dob)) / (365.25 * 24 * 60 * 60 * 1000))
778
+ * return age
779
+ * })
780
+ *
781
+ * @remarks
782
+ * Limitations:
783
+ * - Client-side only. For backend computation, use country config event triggers.
784
+ * - Cannot reference external libraries (lodash, etc.)
785
+ * - Function must be pure — no side effects, no closures over external state
786
+ * - Function must be serialisable via toString()
787
+ * - Receives (value, context) parameters where context contains $form, $now, $online, etc.
788
+ * - Should return the computed value (any type)
789
+ *
790
+ * @see https://github.com/opencrvs/opencrvs-core/issues/11653
791
+ */
792
+ customClientEvaluation(fn) {
793
+ const serializedFn = fn.toString();
794
+ return {
795
+ $$code: serializedFn
796
+ };
797
+ },
688
798
  getId: () => ({ fieldId }),
689
799
  /**
690
800
  * @deprecated
@@ -71,6 +71,7 @@ __export(events_exports, {
71
71
  CheckboxFieldValue: () => CheckboxFieldValue,
72
72
  Clause: () => Clause,
73
73
  ClientSpecificAction: () => ClientSpecificAction,
74
+ CodeToEvaluate: () => CodeToEvaluate,
74
75
  Conditional: () => Conditional,
75
76
  ConditionalType: () => ConditionalType,
76
77
  ConfirmableActions: () => ConfirmableActions,
@@ -322,6 +323,7 @@ __export(events_exports, {
322
323
  isBulletListFieldType: () => isBulletListFieldType,
323
324
  isButtonFieldType: () => isButtonFieldType,
324
325
  isCheckboxFieldType: () => isCheckboxFieldType,
326
+ isCodeToEvaluate: () => isCodeToEvaluate,
325
327
  isConditionMet: () => isConditionMet,
326
328
  isCountryFieldType: () => isCountryFieldType,
327
329
  isCustomFieldType: () => isCustomFieldType,
@@ -2036,6 +2038,11 @@ var FieldReference = z17.object({
2036
2038
  'If the FieldValue is an object, subfield can be used to refer to e.g. `["foo", "bar"]` in `{ foo: { bar: 3 } }`'
2037
2039
  )
2038
2040
  }).describe("Reference to a field by its ID");
2041
+ var CodeToEvaluate = z17.object({
2042
+ $$code: z17.string().describe(
2043
+ "Serialized function string that will be evaluated at runtime with the field value and context"
2044
+ )
2045
+ }).describe("Custom evaluation function for computed field values");
2039
2046
  var ValidationConfig = z17.object({
2040
2047
  validator: Conditional,
2041
2048
  message: TranslationConfig
@@ -2068,7 +2075,7 @@ var BaseField = z17.object({
2068
2075
  uncorrectable: z17.boolean().default(false).optional().describe(
2069
2076
  "Indicates whether the field can be modified during record correction."
2070
2077
  ),
2071
- value: FieldReference.or(z17.array(FieldReference)).optional().describe(
2078
+ value: FieldReference.or(CodeToEvaluate).or(z17.array(FieldReference.or(CodeToEvaluate))).optional().describe(
2072
2079
  "Reference to the source field or fields. When a value is defined, it is copied from the parent field when changed. If multiple references are provided, the first truthy value is used."
2073
2080
  ),
2074
2081
  analytics: z17.boolean().default(false).optional().describe(
@@ -2410,7 +2417,7 @@ var Address = BaseField.extend({
2410
2417
  var StaticDataEntry = z17.object({
2411
2418
  id: z17.string().describe("ID for the data entry."),
2412
2419
  label: TranslationConfig,
2413
- value: TranslationConfig.or(z17.string()).or(FieldReference)
2420
+ value: TranslationConfig.or(z17.string()).or(FieldReference).or(CodeToEvaluate)
2414
2421
  }).describe("Static data entry");
2415
2422
  var DataEntry = z17.union([StaticDataEntry, z17.object({ fieldId: z17.string() })]).describe(
2416
2423
  "Data entry can be either a static data entry, or a reference to another field in the current form or the declaration."
@@ -2452,7 +2459,7 @@ var HttpField = BaseField.extend({
2452
2459
  headers: z17.record(z17.string(), z17.string()).optional(),
2453
2460
  body: z17.record(z17.string(), z17.any()).optional(),
2454
2461
  errorValue: z17.any().optional().describe("Value to set if the request fails"),
2455
- params: z17.record(z17.string(), z17.union([z17.string(), FieldReference])).optional(),
2462
+ params: z17.record(z17.string(), z17.union([z17.string(), FieldReference, CodeToEvaluate])).optional(),
2456
2463
  timeout: z17.number().default(15e3).describe("Request timeout in milliseconds")
2457
2464
  })
2458
2465
  }).describe("HTTP request function triggered by a button click or other event");
@@ -3594,31 +3601,57 @@ ajv.addKeyword({
3594
3601
  return locations.some((location) => location.id === locationIdInput);
3595
3602
  }
3596
3603
  });
3604
+ ajv.addKeyword({
3605
+ keyword: "customClientValidator",
3606
+ type: ["string", "number", "boolean", "object", "array", "null"],
3607
+ schemaType: "string",
3608
+ $data: false,
3609
+ errors: false,
3610
+ validate(schema, data, _, dataContext) {
3611
+ if (!schema || typeof schema !== "string") {
3612
+ return true;
3613
+ }
3614
+ if (typeof window === "undefined") {
3615
+ return true;
3616
+ }
3617
+ try {
3618
+ const validatorFn = new Function(
3619
+ "value",
3620
+ "context",
3621
+ `return (${schema})(value, context)`
3622
+ );
3623
+ const context = dataContext?.rootData || {};
3624
+ const result = validatorFn(data, context);
3625
+ return Boolean(result);
3626
+ } catch (error) {
3627
+ return false;
3628
+ }
3629
+ }
3630
+ });
3631
+ function isAgeValue(value) {
3632
+ return typeof value === "object" && value !== null && "age" in value && typeof value.age === "number";
3633
+ }
3597
3634
  function validate(schema, data) {
3598
3635
  const validator = ajv.getSchema(schema.$id) || ajv.compile(schema);
3599
3636
  if ("$form" in data) {
3600
- data.$form = Object.fromEntries(
3601
- Object.entries(data.$form).map(([key, value]) => {
3602
- const maybeAgeValue = AgeValue.safeParse(value);
3603
- if (maybeAgeValue.success) {
3604
- const age = maybeAgeValue.data.age;
3605
- const maybeAsOfDate = DateValue.safeParse(
3606
- data.$form[maybeAgeValue.data.asOfDateRef]
3607
- );
3608
- return [
3609
- key,
3610
- {
3611
- age,
3612
- dob: ageToDate(
3613
- age,
3614
- maybeAsOfDate.success ? maybeAsOfDate.data : data.$now
3615
- )
3616
- }
3617
- ];
3618
- }
3637
+ const entries = Object.entries(data.$form).map(([key, value]) => {
3638
+ if (!isAgeValue(value)) {
3619
3639
  return [key, value];
3620
- })
3621
- );
3640
+ }
3641
+ const age = value.age;
3642
+ const maybeAsOfDate = DateValue.safeParse(data.$form[value.asOfDateRef]);
3643
+ return [
3644
+ key,
3645
+ {
3646
+ age,
3647
+ dob: ageToDate(
3648
+ age,
3649
+ maybeAsOfDate.success ? maybeAsOfDate.data : data.$now
3650
+ )
3651
+ }
3652
+ ];
3653
+ });
3654
+ data.$form = Object.fromEntries(entries);
3622
3655
  }
3623
3656
  const result = validator(data);
3624
3657
  return result;
@@ -4629,6 +4662,9 @@ var user = Object.assign(userSerializer, {
4629
4662
  function isFieldReference(value) {
4630
4663
  return typeof value === "object" && value !== null && "$$field" in value;
4631
4664
  }
4665
+ function isCodeToEvaluate(value) {
4666
+ return typeof value === "object" && value !== null && "$$code" in value;
4667
+ }
4632
4668
  function getDateRangeToFieldReference(field3, comparedField, clause) {
4633
4669
  return {
4634
4670
  type: "object",
@@ -5065,6 +5101,112 @@ function createFieldConditionals(fieldId) {
5065
5101
  required: [fieldId]
5066
5102
  });
5067
5103
  },
5104
+ /**
5105
+ * Executes a custom validation function defined by country configuration.
5106
+ *
5107
+ * **Use this as a last resort when predefined toolkit methods are insufficient.**
5108
+ *
5109
+ * The function is serialized via toString() and transmitted as part of the JSON Schema.
5110
+ * It is deserialized just-in-time during validation on the client.
5111
+ *
5112
+ * @param fn - Validation function that receives the field value and form context.
5113
+ * Must return true if valid, false if invalid.
5114
+ *
5115
+ * @returns JSONSchema conditional for AJV validation
5116
+ *
5117
+ * @example
5118
+ * // Simple age validation
5119
+ * field('age').customClientValidator((value, ctx) => {
5120
+ * return value >= 18
5121
+ * })
5122
+ *
5123
+ * @example
5124
+ * // Cross-field validation: child DOB must be after mother DOB
5125
+ * field('child.dob').customClientValidator((value, ctx) => {
5126
+ * const motherDob = ctx.$form['mother.dob']
5127
+ * if (!motherDob || !value) return false
5128
+ * return new Date(value) > new Date(motherDob)
5129
+ * })
5130
+ *
5131
+ * @example
5132
+ * // Two number fields validated together
5133
+ * field('fieldA').customClientValidator((value, ctx) => {
5134
+ * const fieldB = ctx.$form['fieldB']
5135
+ * if (!fieldB) return false
5136
+ * return value + fieldB > 100
5137
+ * })
5138
+ *
5139
+ * @remarks
5140
+ * Limitations:
5141
+ * - Client-side only. For backend validation, use country config event triggers.
5142
+ * - Cannot reference external libraries (lodash, etc.)
5143
+ * - Function must be pure — no side effects, no closures over external state
5144
+ * - Function must be serialisable via toString()
5145
+ * - Receives (value, context) parameters where context contains $form, $now, $online, etc.
5146
+ * - Must return boolean: true = valid, false = invalid
5147
+ *
5148
+ * @see https://github.com/opencrvs/opencrvs-core/issues/11653
5149
+ */
5150
+ customClientValidator(fn) {
5151
+ const serializedFn = fn.toString();
5152
+ return defineFormConditional({
5153
+ type: "object",
5154
+ properties: {
5155
+ [fieldId]: wrapToPath(
5156
+ {
5157
+ customClientValidator: serializedFn
5158
+ },
5159
+ this.$$subfield
5160
+ )
5161
+ },
5162
+ required: [fieldId]
5163
+ });
5164
+ },
5165
+ /**
5166
+ * Defines a client-side computation function for dynamic field values.
5167
+ *
5168
+ * **Use this as a last resort when predefined toolkit methods are insufficient.**
5169
+ *
5170
+ * The function is serialized via toString() and transmitted to the client,
5171
+ * where it is deserialized and executed to compute field values dynamically.
5172
+ *
5173
+ * @param fn - Computation function that receives the field value and form context.
5174
+ * Returns the computed value (any type).
5175
+ *
5176
+ * @returns CodeToEvaluate object for client-side execution
5177
+ *
5178
+ * @example
5179
+ * // Concatenate first and last name
5180
+ * field('fullName').customClientEvaluation((value, ctx) => {
5181
+ * return `${ctx.$form.firstName} ${ctx.$form.lastName}`
5182
+ * })
5183
+ *
5184
+ * @example
5185
+ * // Calculate age from date of birth
5186
+ * field('childAge').customClientEvaluation((value, ctx) => {
5187
+ * const dob = ctx.$form['child.dob']
5188
+ * if (!dob) return undefined
5189
+ * const age = Math.floor((new Date(ctx.$now) - new Date(dob)) / (365.25 * 24 * 60 * 60 * 1000))
5190
+ * return age
5191
+ * })
5192
+ *
5193
+ * @remarks
5194
+ * Limitations:
5195
+ * - Client-side only. For backend computation, use country config event triggers.
5196
+ * - Cannot reference external libraries (lodash, etc.)
5197
+ * - Function must be pure — no side effects, no closures over external state
5198
+ * - Function must be serialisable via toString()
5199
+ * - Receives (value, context) parameters where context contains $form, $now, $online, etc.
5200
+ * - Should return the computed value (any type)
5201
+ *
5202
+ * @see https://github.com/opencrvs/opencrvs-core/issues/11653
5203
+ */
5204
+ customClientEvaluation(fn) {
5205
+ const serializedFn = fn.toString();
5206
+ return {
5207
+ $$code: serializedFn
5208
+ };
5209
+ },
5068
5210
  getId: () => ({ fieldId }),
5069
5211
  /**
5070
5212
  * @deprecated
@@ -1532,6 +1532,11 @@ var FieldReference = z17.object({
1532
1532
  'If the FieldValue is an object, subfield can be used to refer to e.g. `["foo", "bar"]` in `{ foo: { bar: 3 } }`'
1533
1533
  )
1534
1534
  }).describe("Reference to a field by its ID");
1535
+ var CodeToEvaluate = z17.object({
1536
+ $$code: z17.string().describe(
1537
+ "Serialized function string that will be evaluated at runtime with the field value and context"
1538
+ )
1539
+ }).describe("Custom evaluation function for computed field values");
1535
1540
  var ValidationConfig = z17.object({
1536
1541
  validator: Conditional,
1537
1542
  message: TranslationConfig
@@ -1564,7 +1569,7 @@ var BaseField = z17.object({
1564
1569
  uncorrectable: z17.boolean().default(false).optional().describe(
1565
1570
  "Indicates whether the field can be modified during record correction."
1566
1571
  ),
1567
- value: FieldReference.or(z17.array(FieldReference)).optional().describe(
1572
+ value: FieldReference.or(CodeToEvaluate).or(z17.array(FieldReference.or(CodeToEvaluate))).optional().describe(
1568
1573
  "Reference to the source field or fields. When a value is defined, it is copied from the parent field when changed. If multiple references are provided, the first truthy value is used."
1569
1574
  ),
1570
1575
  analytics: z17.boolean().default(false).optional().describe(
@@ -1906,7 +1911,7 @@ var Address = BaseField.extend({
1906
1911
  var StaticDataEntry = z17.object({
1907
1912
  id: z17.string().describe("ID for the data entry."),
1908
1913
  label: TranslationConfig,
1909
- value: TranslationConfig.or(z17.string()).or(FieldReference)
1914
+ value: TranslationConfig.or(z17.string()).or(FieldReference).or(CodeToEvaluate)
1910
1915
  }).describe("Static data entry");
1911
1916
  var DataEntry = z17.union([StaticDataEntry, z17.object({ fieldId: z17.string() })]).describe(
1912
1917
  "Data entry can be either a static data entry, or a reference to another field in the current form or the declaration."
@@ -1948,7 +1953,7 @@ var HttpField = BaseField.extend({
1948
1953
  headers: z17.record(z17.string(), z17.string()).optional(),
1949
1954
  body: z17.record(z17.string(), z17.any()).optional(),
1950
1955
  errorValue: z17.any().optional().describe("Value to set if the request fails"),
1951
- params: z17.record(z17.string(), z17.union([z17.string(), FieldReference])).optional(),
1956
+ params: z17.record(z17.string(), z17.union([z17.string(), FieldReference, CodeToEvaluate])).optional(),
1952
1957
  timeout: z17.number().default(15e3).describe("Request timeout in milliseconds")
1953
1958
  })
1954
1959
  }).describe("HTTP request function triggered by a button click or other event");
@@ -2780,6 +2785,33 @@ ajv.addKeyword({
2780
2785
  return locations.some((location) => location.id === locationIdInput);
2781
2786
  }
2782
2787
  });
2788
+ ajv.addKeyword({
2789
+ keyword: "customClientValidator",
2790
+ type: ["string", "number", "boolean", "object", "array", "null"],
2791
+ schemaType: "string",
2792
+ $data: false,
2793
+ errors: false,
2794
+ validate(schema, data, _, dataContext) {
2795
+ if (!schema || typeof schema !== "string") {
2796
+ return true;
2797
+ }
2798
+ if (typeof window === "undefined") {
2799
+ return true;
2800
+ }
2801
+ try {
2802
+ const validatorFn = new Function(
2803
+ "value",
2804
+ "context",
2805
+ `return (${schema})(value, context)`
2806
+ );
2807
+ const context = dataContext?.rootData || {};
2808
+ const result = validatorFn(data, context);
2809
+ return Boolean(result);
2810
+ } catch (error) {
2811
+ return false;
2812
+ }
2813
+ }
2814
+ });
2783
2815
 
2784
2816
  // ../commons/src/utils.ts
2785
2817
  var z29 = __toESM(require("zod/v4"));
@@ -3535,6 +3567,112 @@ function createFieldConditionals(fieldId) {
3535
3567
  required: [fieldId]
3536
3568
  });
3537
3569
  },
3570
+ /**
3571
+ * Executes a custom validation function defined by country configuration.
3572
+ *
3573
+ * **Use this as a last resort when predefined toolkit methods are insufficient.**
3574
+ *
3575
+ * The function is serialized via toString() and transmitted as part of the JSON Schema.
3576
+ * It is deserialized just-in-time during validation on the client.
3577
+ *
3578
+ * @param fn - Validation function that receives the field value and form context.
3579
+ * Must return true if valid, false if invalid.
3580
+ *
3581
+ * @returns JSONSchema conditional for AJV validation
3582
+ *
3583
+ * @example
3584
+ * // Simple age validation
3585
+ * field('age').customClientValidator((value, ctx) => {
3586
+ * return value >= 18
3587
+ * })
3588
+ *
3589
+ * @example
3590
+ * // Cross-field validation: child DOB must be after mother DOB
3591
+ * field('child.dob').customClientValidator((value, ctx) => {
3592
+ * const motherDob = ctx.$form['mother.dob']
3593
+ * if (!motherDob || !value) return false
3594
+ * return new Date(value) > new Date(motherDob)
3595
+ * })
3596
+ *
3597
+ * @example
3598
+ * // Two number fields validated together
3599
+ * field('fieldA').customClientValidator((value, ctx) => {
3600
+ * const fieldB = ctx.$form['fieldB']
3601
+ * if (!fieldB) return false
3602
+ * return value + fieldB > 100
3603
+ * })
3604
+ *
3605
+ * @remarks
3606
+ * Limitations:
3607
+ * - Client-side only. For backend validation, use country config event triggers.
3608
+ * - Cannot reference external libraries (lodash, etc.)
3609
+ * - Function must be pure — no side effects, no closures over external state
3610
+ * - Function must be serialisable via toString()
3611
+ * - Receives (value, context) parameters where context contains $form, $now, $online, etc.
3612
+ * - Must return boolean: true = valid, false = invalid
3613
+ *
3614
+ * @see https://github.com/opencrvs/opencrvs-core/issues/11653
3615
+ */
3616
+ customClientValidator(fn) {
3617
+ const serializedFn = fn.toString();
3618
+ return defineFormConditional({
3619
+ type: "object",
3620
+ properties: {
3621
+ [fieldId]: wrapToPath(
3622
+ {
3623
+ customClientValidator: serializedFn
3624
+ },
3625
+ this.$$subfield
3626
+ )
3627
+ },
3628
+ required: [fieldId]
3629
+ });
3630
+ },
3631
+ /**
3632
+ * Defines a client-side computation function for dynamic field values.
3633
+ *
3634
+ * **Use this as a last resort when predefined toolkit methods are insufficient.**
3635
+ *
3636
+ * The function is serialized via toString() and transmitted to the client,
3637
+ * where it is deserialized and executed to compute field values dynamically.
3638
+ *
3639
+ * @param fn - Computation function that receives the field value and form context.
3640
+ * Returns the computed value (any type).
3641
+ *
3642
+ * @returns CodeToEvaluate object for client-side execution
3643
+ *
3644
+ * @example
3645
+ * // Concatenate first and last name
3646
+ * field('fullName').customClientEvaluation((value, ctx) => {
3647
+ * return `${ctx.$form.firstName} ${ctx.$form.lastName}`
3648
+ * })
3649
+ *
3650
+ * @example
3651
+ * // Calculate age from date of birth
3652
+ * field('childAge').customClientEvaluation((value, ctx) => {
3653
+ * const dob = ctx.$form['child.dob']
3654
+ * if (!dob) return undefined
3655
+ * const age = Math.floor((new Date(ctx.$now) - new Date(dob)) / (365.25 * 24 * 60 * 60 * 1000))
3656
+ * return age
3657
+ * })
3658
+ *
3659
+ * @remarks
3660
+ * Limitations:
3661
+ * - Client-side only. For backend computation, use country config event triggers.
3662
+ * - Cannot reference external libraries (lodash, etc.)
3663
+ * - Function must be pure — no side effects, no closures over external state
3664
+ * - Function must be serialisable via toString()
3665
+ * - Receives (value, context) parameters where context contains $form, $now, $online, etc.
3666
+ * - Should return the computed value (any type)
3667
+ *
3668
+ * @see https://github.com/opencrvs/opencrvs-core/issues/11653
3669
+ */
3670
+ customClientEvaluation(fn) {
3671
+ const serializedFn = fn.toString();
3672
+ return {
3673
+ $$code: serializedFn
3674
+ };
3675
+ },
3538
3676
  getId: () => ({ fieldId }),
3539
3677
  /**
3540
3678
  * @deprecated
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencrvs/toolkit",
3
- "version": "1.9.8-rc.34c53bf",
3
+ "version": "1.9.8-rc.48851c5",
4
4
  "description": "OpenCRVS toolkit for building country configurations",
5
5
  "license": "MPL-2.0",
6
6
  "exports": {