form0-core 0.1.6 → 0.1.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "form0-core",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/"
6
6
  },
package/src/index.js CHANGED
@@ -35,6 +35,7 @@ export {
35
35
  getCalculationBuiltinCatalog,
36
36
  getCalculationReferenceCatalog,
37
37
  analyzeCalculationExpression,
38
+ createCalculationPreviewSession,
38
39
  simulateCalculationExpression,
39
40
  } from './utilities/calculation-editor.js';
40
41
  export {
@@ -296,6 +296,33 @@ function sanitizeCollectedWarnings(warnings) {
296
296
  }));
297
297
  }
298
298
 
299
+ function clearRecord(record) {
300
+ if (!record || typeof record !== 'object') {
301
+ return;
302
+ }
303
+
304
+ Object.keys(record).forEach((key) => {
305
+ delete record[key];
306
+ });
307
+ }
308
+
309
+ function resetEngineValues(targetValues, sourceValues) {
310
+ clearRecord(targetValues);
311
+ Object.assign(targetValues, deepClone(sourceValues));
312
+ }
313
+
314
+ function normalizeSimulationValues(values) {
315
+ return values && typeof values === 'object' && !Array.isArray(values) ? values : {};
316
+ }
317
+
318
+ function createSimulationErrorResult(runtimeError, warnings = []) {
319
+ return {
320
+ result: null,
321
+ runtimeError,
322
+ warnings,
323
+ };
324
+ }
325
+
299
326
  /**
300
327
  * @typedef {'stable' | 'advanced' | 'unavailable'} CalculationBuiltinStatus
301
328
  *
@@ -615,30 +642,34 @@ export function analyzeCalculationExpression({
615
642
  }
616
643
 
617
644
  /**
618
- * Simulate a CalculatedField expression against form0-core's real evaluation engine.
645
+ * Create a reusable preview session for CalculatedField authoring.
619
646
  *
620
647
  * @param {Object} options
621
648
  * @param {Object} options.schema
622
649
  * @param {string} options.fieldDataName
623
650
  * @param {string} options.expression
624
- * @param {Object} [options.values]
625
651
  * @param {Object} [options.security]
626
652
  * @returns {{
627
- * result: unknown,
628
- * runtimeError: string | null,
629
- * warnings: Array<{
630
- * message: string,
631
- * suggestion: string | null,
632
- * context: Object | null,
633
- * fieldContext: Object | null,
634
- * }>,
653
+ * run: (options?: {
654
+ * expression?: string,
655
+ * values?: Object,
656
+ * }) => {
657
+ * result: unknown,
658
+ * runtimeError: string | null,
659
+ * warnings: Array<{
660
+ * message: string,
661
+ * suggestion: string | null,
662
+ * context: Object | null,
663
+ * fieldContext: Object | null,
664
+ * }>,
665
+ * },
666
+ * dispose: () => void,
635
667
  * }}
636
668
  */
637
- export function simulateCalculationExpression({
669
+ export function createCalculationPreviewSession({
638
670
  schema,
639
671
  fieldDataName,
640
672
  expression,
641
- values = {},
642
673
  security = DEFAULT_SECURITY_CONFIG,
643
674
  }) {
644
675
  const simulationSchema = cloneSimulationSchema(schema);
@@ -647,17 +678,19 @@ export function simulateCalculationExpression({
647
678
 
648
679
  if (!targetField) {
649
680
  return {
650
- result: null,
651
- runtimeError: `Field '${fieldDataName}' does not exist in the form schema.`,
652
- warnings: [],
681
+ run: () =>
682
+ createSimulationErrorResult(
683
+ `Field '${fieldDataName}' does not exist in the form schema.`
684
+ ),
685
+ dispose: () => {},
653
686
  };
654
687
  }
655
688
 
656
689
  if (targetField.type !== 'CalculatedField') {
657
690
  return {
658
- result: null,
659
- runtimeError: `Field '${fieldDataName}' is not a CalculatedField.`,
660
- warnings: [],
691
+ run: () =>
692
+ createSimulationErrorResult(`Field '${fieldDataName}' is not a CalculatedField.`),
693
+ dispose: () => {},
661
694
  };
662
695
  }
663
696
 
@@ -666,41 +699,113 @@ export function simulateCalculationExpression({
666
699
  const warningSystem = new WarningSystem({
667
700
  enableCollection: true,
668
701
  enableConsoleWarnings: false,
702
+ throttleMs: 0,
669
703
  });
670
704
  const runtimeDiagnostics = [];
705
+ const engine = createFormEngine({
706
+ schema: simulationSchema,
707
+ initialValues: {},
708
+ security,
709
+ warningSystem,
710
+ runtimeDiagnostics,
711
+ });
671
712
 
672
- try {
673
- const engine = createFormEngine({
674
- schema: simulationSchema,
675
- initialValues:
676
- values && typeof values === 'object' && !Array.isArray(values) ? values : {},
677
- security,
678
- warningSystem,
679
- runtimeDiagnostics,
680
- });
713
+ engine.eval();
681
714
 
682
- engine.eval();
715
+ const engineState = engine.getState();
716
+ const baselineValues = deepClone(engineState.values);
717
+ let currentExpression = targetField.calculate;
718
+ let disposed = false;
683
719
 
684
- const engineState = engine.getState();
685
- const simulatedResult = engineState.values[fieldDataName];
686
- const runtimeError =
687
- runtimeDiagnostics.find((diagnostic) => diagnostic.fieldName === fieldDataName)?.message ||
688
- runtimeDiagnostics[0]?.message ||
689
- null;
720
+ return {
721
+ run: ({ expression: nextExpression, values = {} } = {}) => {
722
+ if (disposed) {
723
+ return createSimulationErrorResult('Calculation preview session has been disposed.');
724
+ }
690
725
 
691
- return {
692
- result: simulatedResult === undefined ? null : simulatedResult,
693
- runtimeError,
694
- warnings: sanitizeCollectedWarnings(warningSystem.getCollectedWarnings()),
695
- };
696
- } catch (error) {
697
- return {
698
- result: null,
699
- runtimeError:
700
- error instanceof Error && error.message
701
- ? error.message
702
- : 'Unknown calculation simulation error.',
703
- warnings: sanitizeCollectedWarnings(warningSystem.getCollectedWarnings()),
704
- };
726
+ warningSystem.clearCollectedWarnings();
727
+ runtimeDiagnostics.length = 0;
728
+
729
+ if (typeof nextExpression === 'string' && nextExpression !== currentExpression) {
730
+ targetField.calculate = nextExpression;
731
+ currentExpression = nextExpression;
732
+ }
733
+
734
+ resetEngineValues(engineState.values, baselineValues);
735
+ Object.assign(engineState.values, normalizeSimulationValues(values));
736
+
737
+ try {
738
+ engine.eval();
739
+
740
+ const simulatedResult = engineState.values[fieldDataName];
741
+ const runtimeError =
742
+ runtimeDiagnostics.find((diagnostic) => diagnostic.fieldName === fieldDataName)
743
+ ?.message ||
744
+ runtimeDiagnostics[0]?.message ||
745
+ null;
746
+
747
+ return {
748
+ result: simulatedResult === undefined ? null : simulatedResult,
749
+ runtimeError,
750
+ warnings: sanitizeCollectedWarnings(warningSystem.getCollectedWarnings()),
751
+ };
752
+ } catch (error) {
753
+ return createSimulationErrorResult(
754
+ error instanceof Error && error.message
755
+ ? error.message
756
+ : 'Unknown calculation simulation error.',
757
+ sanitizeCollectedWarnings(warningSystem.getCollectedWarnings())
758
+ );
759
+ }
760
+ },
761
+ dispose: () => {
762
+ disposed = true;
763
+ warningSystem.clearCollectedWarnings();
764
+ runtimeDiagnostics.length = 0;
765
+ clearRecord(engineState.values);
766
+ },
767
+ };
768
+ }
769
+
770
+ /**
771
+ * Simulate a CalculatedField expression against form0-core's real evaluation engine.
772
+ *
773
+ * @param {Object} options
774
+ * @param {Object} options.schema
775
+ * @param {string} options.fieldDataName
776
+ * @param {string} options.expression
777
+ * @param {Object} [options.values]
778
+ * @param {Object} [options.security]
779
+ * @returns {{
780
+ * result: unknown,
781
+ * runtimeError: string | null,
782
+ * warnings: Array<{
783
+ * message: string,
784
+ * suggestion: string | null,
785
+ * context: Object | null,
786
+ * fieldContext: Object | null,
787
+ * }>,
788
+ * }}
789
+ */
790
+ export function simulateCalculationExpression({
791
+ schema,
792
+ fieldDataName,
793
+ expression,
794
+ values = {},
795
+ security = DEFAULT_SECURITY_CONFIG,
796
+ }) {
797
+ const session = createCalculationPreviewSession({
798
+ schema,
799
+ fieldDataName,
800
+ expression,
801
+ security,
802
+ });
803
+ try {
804
+ return session.run({
805
+ expression,
806
+ values,
807
+ });
808
+ } finally {
809
+ session.dispose();
705
810
  }
706
811
  }