@dxos/plugin-sheet 0.6.12-main.f9d0246 → 0.6.12-staging.e11e696

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.
Files changed (114) hide show
  1. package/dist/lib/browser/{SheetContainer-VISF3VUB.mjs → SheetContainer-LG77O4RM.mjs} +11 -10
  2. package/dist/lib/browser/SheetContainer-LG77O4RM.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-ZLJ2GRE2.mjs → chunk-CHQAW4F4.mjs} +55 -33
  4. package/dist/lib/browser/chunk-CHQAW4F4.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-Z2XOOC2R.mjs → chunk-GSV5QNLD.mjs} +183 -159
  6. package/dist/lib/browser/chunk-GSV5QNLD.mjs.map +7 -0
  7. package/dist/lib/browser/graph-M4IQ76QX.mjs +33 -0
  8. package/dist/lib/browser/index.mjs +37 -15
  9. package/dist/lib/browser/index.mjs.map +4 -4
  10. package/dist/lib/browser/meta.json +1 -1
  11. package/dist/lib/node/{SheetContainer-2MEALQWW.cjs → SheetContainer-OZ7DHH4L.cjs} +18 -17
  12. package/dist/lib/node/SheetContainer-OZ7DHH4L.cjs.map +7 -0
  13. package/dist/lib/node/{chunk-6DQABRGJ.cjs → chunk-5FTFZL5W.cjs} +57 -35
  14. package/dist/lib/node/chunk-5FTFZL5W.cjs.map +7 -0
  15. package/dist/lib/node/{chunk-P5QYYEHQ.cjs → chunk-5XPK2V4A.cjs} +186 -158
  16. package/dist/lib/node/chunk-5XPK2V4A.cjs.map +7 -0
  17. package/dist/lib/node/graph-Q3N2X26H.cjs +55 -0
  18. package/dist/lib/node/graph-Q3N2X26H.cjs.map +7 -0
  19. package/dist/lib/node/index.cjs +38 -18
  20. package/dist/lib/node/index.cjs.map +4 -4
  21. package/dist/lib/node/meta.json +1 -1
  22. package/dist/lib/node-esm/{SheetContainer-RPSUSXWS.mjs → SheetContainer-4XS2G25Z.mjs} +11 -10
  23. package/dist/lib/node-esm/SheetContainer-4XS2G25Z.mjs.map +7 -0
  24. package/dist/lib/node-esm/{chunk-4MM7THJW.mjs → chunk-5WPZCXNS.mjs} +183 -159
  25. package/dist/lib/node-esm/chunk-5WPZCXNS.mjs.map +7 -0
  26. package/dist/lib/node-esm/{chunk-5RLTCIE2.mjs → chunk-KK3XL37M.mjs} +55 -33
  27. package/dist/lib/node-esm/chunk-KK3XL37M.mjs.map +7 -0
  28. package/dist/lib/node-esm/graph-SMPUMOV2.mjs +34 -0
  29. package/dist/lib/node-esm/index.mjs +37 -15
  30. package/dist/lib/node-esm/index.mjs.map +4 -4
  31. package/dist/lib/node-esm/meta.json +1 -1
  32. package/dist/types/src/SheetPlugin.d.ts.map +1 -1
  33. package/dist/types/src/components/CellEditor/CellEditor.stories.d.ts.map +1 -1
  34. package/dist/types/src/components/CellEditor/extension.d.ts.map +1 -1
  35. package/dist/types/src/components/Sheet/Sheet.d.ts.map +1 -1
  36. package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/SheetContainer.d.ts.map +1 -1
  38. package/dist/types/src/components/index.d.ts.map +1 -1
  39. package/dist/types/src/extensions/compute.d.ts +2 -5
  40. package/dist/types/src/extensions/compute.d.ts.map +1 -1
  41. package/dist/types/src/extensions/compute.stories.d.ts.map +1 -1
  42. package/dist/types/src/graph/compute-graph-registry.d.ts +34 -0
  43. package/dist/types/src/graph/compute-graph-registry.d.ts.map +1 -0
  44. package/dist/types/src/graph/compute-graph.d.ts +13 -33
  45. package/dist/types/src/graph/compute-graph.d.ts.map +1 -1
  46. package/dist/types/src/graph/compute-node.d.ts.map +1 -1
  47. package/dist/types/src/graph/{async-function.d.ts → functions/async-function.d.ts} +6 -3
  48. package/dist/types/src/graph/functions/async-function.d.ts.map +1 -0
  49. package/dist/types/src/graph/functions/edge-function.d.ts +21 -0
  50. package/dist/types/src/graph/functions/edge-function.d.ts.map +1 -0
  51. package/dist/types/src/graph/functions/function-defs.d.ts.map +1 -0
  52. package/dist/types/src/graph/functions/index.d.ts +4 -0
  53. package/dist/types/src/graph/functions/index.d.ts.map +1 -0
  54. package/dist/types/src/graph/index.d.ts +2 -1
  55. package/dist/types/src/graph/index.d.ts.map +1 -1
  56. package/dist/types/src/graph/testing/index.d.ts +2 -1
  57. package/dist/types/src/graph/testing/index.d.ts.map +1 -1
  58. package/dist/types/src/graph/testing/test-builder.d.ts +15 -0
  59. package/dist/types/src/graph/testing/test-builder.d.ts.map +1 -0
  60. package/dist/types/src/graph/testing/test-plugin.d.ts +36 -0
  61. package/dist/types/src/graph/testing/test-plugin.d.ts.map +1 -0
  62. package/dist/types/src/hooks/useComputeGraph.d.ts.map +1 -1
  63. package/dist/types/src/model/sheet-model.d.ts.map +1 -1
  64. package/dist/types/src/model/sheet-model.test.d.ts +2 -0
  65. package/dist/types/src/model/sheet-model.test.d.ts.map +1 -0
  66. package/package.json +40 -39
  67. package/src/SheetPlugin.tsx +12 -10
  68. package/src/components/CellEditor/CellEditor.stories.tsx +1 -2
  69. package/src/components/CellEditor/extension.test.ts +0 -1
  70. package/src/components/CellEditor/extension.ts +4 -3
  71. package/src/components/Sheet/Sheet.stories.tsx +2 -2
  72. package/src/components/Sheet/Sheet.tsx +30 -14
  73. package/src/components/SheetContainer.tsx +11 -13
  74. package/src/extensions/compute.stories.tsx +9 -11
  75. package/src/extensions/compute.ts +66 -50
  76. package/src/graph/compute-graph-registry.ts +90 -0
  77. package/src/graph/compute-graph.stories.tsx +2 -2
  78. package/src/graph/compute-graph.test.ts +31 -71
  79. package/src/graph/compute-graph.ts +45 -116
  80. package/src/graph/compute-node.ts +1 -0
  81. package/src/graph/{async-function.ts → functions/async-function.ts} +10 -9
  82. package/src/graph/{edge-function.ts → functions/edge-function.ts} +13 -11
  83. package/src/graph/functions/index.ts +7 -0
  84. package/src/graph/hyperformula.test.ts +1 -2
  85. package/src/graph/index.ts +2 -1
  86. package/src/graph/testing/index.ts +2 -1
  87. package/src/graph/testing/test-builder.ts +54 -0
  88. package/src/graph/testing/{custom-function.ts → test-plugin.ts} +38 -12
  89. package/src/hooks/useComputeGraph.ts +8 -1
  90. package/src/model/sheet-model.test.ts +59 -0
  91. package/src/model/sheet-model.ts +4 -2
  92. package/dist/lib/browser/SheetContainer-VISF3VUB.mjs.map +0 -7
  93. package/dist/lib/browser/chunk-Z2XOOC2R.mjs.map +0 -7
  94. package/dist/lib/browser/chunk-ZLJ2GRE2.mjs.map +0 -7
  95. package/dist/lib/browser/graph-4XFKIHRL.mjs +0 -21
  96. package/dist/lib/node/SheetContainer-2MEALQWW.cjs.map +0 -7
  97. package/dist/lib/node/chunk-6DQABRGJ.cjs.map +0 -7
  98. package/dist/lib/node/chunk-P5QYYEHQ.cjs.map +0 -7
  99. package/dist/lib/node/graph-2LRDUXBZ.cjs +0 -43
  100. package/dist/lib/node/graph-2LRDUXBZ.cjs.map +0 -7
  101. package/dist/lib/node-esm/SheetContainer-RPSUSXWS.mjs.map +0 -7
  102. package/dist/lib/node-esm/chunk-4MM7THJW.mjs.map +0 -7
  103. package/dist/lib/node-esm/chunk-5RLTCIE2.mjs.map +0 -7
  104. package/dist/lib/node-esm/graph-WG5EKOMO.mjs +0 -22
  105. package/dist/types/src/graph/async-function.d.ts.map +0 -1
  106. package/dist/types/src/graph/edge-function.d.ts +0 -20
  107. package/dist/types/src/graph/edge-function.d.ts.map +0 -1
  108. package/dist/types/src/graph/function-defs.d.ts.map +0 -1
  109. package/dist/types/src/graph/testing/custom-function.d.ts +0 -23
  110. package/dist/types/src/graph/testing/custom-function.d.ts.map +0 -1
  111. /package/dist/lib/browser/{graph-4XFKIHRL.mjs.map → graph-M4IQ76QX.mjs.map} +0 -0
  112. /package/dist/lib/node-esm/{graph-WG5EKOMO.mjs.map → graph-SMPUMOV2.mjs.map} +0 -0
  113. /package/dist/types/src/graph/{function-defs.d.ts → functions/function-defs.d.ts} +0 -0
  114. /package/src/graph/{function-defs.ts → functions/function-defs.ts} +0 -0
@@ -3,25 +3,25 @@
3
3
  //
4
4
 
5
5
  import '@dxos-theme';
6
- import React, { useEffect } from 'react';
6
+ import React, { useEffect, useMemo } from 'react';
7
7
 
8
+ import { PublicKey } from '@dxos/keys';
8
9
  import { useSpace } from '@dxos/react-client/echo';
9
10
  import { withClientProvider } from '@dxos/react-client/testing';
10
- import { useAsyncState } from '@dxos/react-hooks';
11
11
  import { useThemeContext } from '@dxos/react-ui';
12
12
  import {
13
13
  createBasicExtensions,
14
14
  createMarkdownExtensions,
15
15
  createThemeExtensions,
16
16
  decorateMarkdown,
17
+ documentId,
17
18
  useTextEditor,
18
19
  } from '@dxos/react-ui-editor';
19
20
  import { withTheme, withLayout } from '@dxos/storybook-utils';
20
21
  import { nonNullable } from '@dxos/util';
21
22
 
22
- import { compute, computeNodeFacet } from './compute';
23
+ import { compute, computeGraphFacet } from './compute';
23
24
  import { Sheet } from '../components';
24
- import { type ComputeNode } from '../graph';
25
25
  import { useComputeGraph, useSheetModel } from '../hooks';
26
26
  import { useTestSheet, withComputeGraphDecorator } from '../testing';
27
27
  import { SheetType } from '../types';
@@ -37,16 +37,13 @@ type EditorProps = {
37
37
 
38
38
  // TODO(burdon): Inline Adobe eCharts.
39
39
 
40
- const DOC_NAME = 'Test Doc';
41
40
  const SHEET_NAME = 'Test Sheet';
42
41
 
43
42
  const Editor = ({ text }: EditorProps) => {
43
+ const id = useMemo(() => PublicKey.random(), []);
44
44
  const { themeMode } = useThemeContext();
45
45
  const space = useSpace();
46
- const graph = useComputeGraph(space);
47
- const [computeNode] = useAsyncState<ComputeNode>(async () => {
48
- return graph ? await graph.getOrCreateNode(DOC_NAME) : undefined;
49
- }, [graph]);
46
+ const computeGraph = useComputeGraph(space);
50
47
  const { parentRef, focusAttributes } = useTextEditor(
51
48
  () => ({
52
49
  initialValue: text,
@@ -54,12 +51,13 @@ const Editor = ({ text }: EditorProps) => {
54
51
  createBasicExtensions(),
55
52
  createMarkdownExtensions({ themeMode }),
56
53
  createThemeExtensions({ themeMode, syntaxHighlighting: true }),
57
- computeNode && computeNodeFacet.of(computeNode),
54
+ documentId.of(id.toHex()),
55
+ computeGraph && computeGraphFacet.of(computeGraph),
58
56
  compute(),
59
57
  decorateMarkdown(),
60
58
  ].filter(nonNullable),
61
59
  }),
62
- [computeNode, themeMode],
60
+ [computeGraph, themeMode],
63
61
  );
64
62
 
65
63
  return <div className='w-[40rem] overflow-hidden' ref={parentRef} {...focusAttributes} />;
@@ -3,7 +3,6 @@
3
3
  //
4
4
 
5
5
  import { syntaxTree } from '@codemirror/language';
6
- import { Facet } from '@codemirror/state';
7
6
  import {
8
7
  type EditorState,
9
8
  type Extension,
@@ -15,64 +14,68 @@ import {
15
14
  } from '@codemirror/state';
16
15
  import { Decoration, EditorView, ViewPlugin, WidgetType } from '@codemirror/view';
17
16
 
18
- import { type UnsubscribeCallback } from '@dxos/async';
19
- import { type Space } from '@dxos/client/echo';
17
+ import { type UnsubscribeCallback, debounce } from '@dxos/async';
18
+ import { invariant } from '@dxos/invariant';
19
+ import { documentId, singleValueFacet } from '@dxos/react-ui-editor/state';
20
20
 
21
21
  import { type CellAddress } from '../defs';
22
- import { type ComputeNode } from '../graph';
22
+ import { type ComputeGraph, type ComputeNode, createSheetName } from '../graph';
23
23
  import { type CellScalarValue } from '../types';
24
24
 
25
- export const spaceFacet = Facet.define<Space, Space>({
26
- combine: (values) => values[0],
27
- });
28
-
29
- // TODO(burdon): Create on demand?
30
- export const computeNodeFacet = Facet.define<ComputeNode, ComputeNode>({
31
- combine: (values) => values[0],
32
- });
33
-
34
25
  const LANGUAGE_TAG = 'dx';
35
26
 
36
27
  // TODO(burdon): Create marker just for our decorator?
37
28
  const updateAllDecorations = StateEffect.define<void>();
38
29
 
30
+ export const computeGraphFacet = singleValueFacet<ComputeGraph>();
31
+
39
32
  export type ComputeOptions = {};
40
33
 
41
34
  export const compute = (options: ComputeOptions = {}): Extension => {
42
- const update = (state: EditorState, rangeSet?: RangeSet<Decoration>) => {
35
+ let computeNode: ComputeNode | undefined;
36
+
37
+ const update = (state: EditorState, current?: RangeSet<Decoration>) => {
43
38
  const builder = new RangeSetBuilder<Decoration>();
44
- const computeNode = state.facet(computeNodeFacet);
45
39
  if (computeNode) {
46
40
  computeNode.clear();
47
41
  syntaxTree(state).iterate({
48
42
  enter: (node) => {
49
- if (node.name === 'FencedCode') {
50
- const cursor = state.selection.main.head;
51
- if (state.readOnly || cursor < node.from || cursor > node.to) {
52
- const info = node.node.getChild('CodeInfo');
53
- if (info) {
54
- const type = state.sliceDoc(info.from, info.to);
55
- const text = node.node.getChild('CodeText');
56
- if (type === LANGUAGE_TAG && text) {
57
- const formula = state.sliceDoc(text.from, text.to);
58
- const iter = rangeSet?.iter(node.node.from);
59
- if (iter?.value && iter?.value.spec.formula === formula) {
60
- builder.add(node.from, node.to, iter.value);
61
- } else {
62
- const cell: CellAddress = { col: node.node.from, row: 0 };
63
- computeNode.setValue(cell, formula);
64
- const value = computeNode.getValue(cell);
65
- builder.add(
66
- node.from,
67
- node.to,
68
- Decoration.replace({
69
- widget: new DxWidget(formula, value),
70
- formula,
71
- }),
72
- );
43
+ switch (node.name) {
44
+ case 'FencedCode': {
45
+ const cursor = state.selection.main.head;
46
+ if (state.readOnly || cursor < node.from || cursor > node.to) {
47
+ const info = node.node.getChild('CodeInfo');
48
+ if (info) {
49
+ const type = state.sliceDoc(info.from, info.to);
50
+ const text = node.node.getChild('CodeText');
51
+ if (type === LANGUAGE_TAG && text) {
52
+ const formula = state.sliceDoc(text.from, text.to);
53
+
54
+ const iter = current?.iter(node.node.from);
55
+ if (iter?.value && iter?.value.spec.formula === formula) {
56
+ // Add existing widget.
57
+ builder.add(node.from, node.to, iter.value);
58
+ } else {
59
+ // TODO(burdon): Create ordered list of cells on each decoration run.
60
+ const cell: CellAddress = { col: node.node.from, row: 0 };
61
+ invariant(computeNode);
62
+ // NOTE: This triggers re-render (below).
63
+ computeNode.setValue(cell, formula);
64
+ const value = computeNode.getValue(cell);
65
+ builder.add(
66
+ node.from,
67
+ node.to,
68
+ Decoration.replace({
69
+ widget: new ComputeWidget(formula, value),
70
+ formula,
71
+ }),
72
+ );
73
+ }
73
74
  }
74
75
  }
75
76
  }
77
+
78
+ break;
76
79
  }
77
80
  }
78
81
  },
@@ -83,25 +86,37 @@ export const compute = (options: ComputeOptions = {}): Extension => {
83
86
  };
84
87
 
85
88
  return [
86
- // Graph subscription.
87
89
  ViewPlugin.fromClass(
88
90
  class {
89
- private readonly _subscription?: UnsubscribeCallback;
91
+ // Graph subscription.
92
+ private _subscription?: UnsubscribeCallback;
90
93
  constructor(view: EditorView) {
91
- const computeNode = view.state.facet(computeNodeFacet);
92
- if (computeNode) {
93
- this._subscription = computeNode.update.on(({ type }) => {
94
- if (type === 'valuesUpdated') {
95
- view.dispatch({
96
- effects: updateAllDecorations.of(),
97
- });
98
- }
94
+ const id = view.state.facet(documentId);
95
+ const computeGraph = view.state.facet(computeGraphFacet);
96
+ if (id && computeGraph) {
97
+ queueMicrotask(async () => {
98
+ computeNode = computeGraph.getOrCreateNode(createSheetName({ type: '', id }));
99
+ await computeNode.open();
100
+
101
+ // Trigger re-render if values updated.
102
+ // TODO(burdon): Trigger only if formula value updated (currently triggered during render).
103
+ this._subscription = computeNode.update.on(
104
+ debounce(({ type, ...rest }) => {
105
+ if (type === 'valuesUpdated') {
106
+ view.dispatch({
107
+ effects: updateAllDecorations.of(),
108
+ });
109
+ }
110
+ }, 250),
111
+ );
99
112
  });
100
113
  }
101
114
  }
102
115
 
103
116
  destroy() {
104
117
  this._subscription?.();
118
+ void computeNode?.close();
119
+ computeNode = undefined;
105
120
  }
106
121
  },
107
122
  ),
@@ -114,7 +129,8 @@ export const compute = (options: ComputeOptions = {}): Extension => {
114
129
  ];
115
130
  };
116
131
 
117
- class DxWidget extends WidgetType {
132
+ // TODO(burdon): Click to edit.
133
+ class ComputeWidget extends WidgetType {
118
134
  constructor(
119
135
  private readonly formula: string,
120
136
  private readonly value: CellScalarValue,
@@ -0,0 +1,90 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import type { FunctionPluginDefinition } from 'hyperformula';
6
+ import type { ConfigParams } from 'hyperformula/typings/ConfigParams';
7
+ import type { FunctionTranslationsPackage } from 'hyperformula/typings/interpreter';
8
+ import defaultsDeep from 'lodash.defaultsdeep';
9
+
10
+ import { type SpaceId, type Space } from '@dxos/client/echo';
11
+ import { Resource } from '@dxos/context';
12
+ import { invariant } from '@dxos/invariant';
13
+ import { log } from '@dxos/log';
14
+
15
+ import { HyperFormula } from '#hyperformula';
16
+ import { ComputeGraph } from './compute-graph';
17
+ import { EdgeFunctionPlugin, EdgeFunctionPluginTranslations, type FunctionContextOptions } from './functions';
18
+
19
+ export type ComputeGraphPlugin = {
20
+ plugin: FunctionPluginDefinition;
21
+ translations: FunctionTranslationsPackage;
22
+ };
23
+
24
+ export type ComputeGraphOptions = {
25
+ plugins?: ComputeGraphPlugin[];
26
+ } & Partial<FunctionContextOptions> &
27
+ Partial<ConfigParams>;
28
+
29
+ export const defaultOptions: ComputeGraphOptions = {
30
+ licenseKey: 'gpl-v3',
31
+ };
32
+
33
+ export const defaultPlugins: ComputeGraphPlugin[] = [
34
+ {
35
+ plugin: EdgeFunctionPlugin,
36
+ translations: EdgeFunctionPluginTranslations,
37
+ },
38
+ ];
39
+
40
+ /**
41
+ * Manages a collection of ComputeGraph instances for each space.
42
+ *
43
+ * [ComputePlugin] => [ComputeGraphRegistry] => [ComputeGraph(Space)] => [ComputeNode(Object)]
44
+ *
45
+ * NOTE: The ComputeGraphRegistry manages the hierarchy of resources via its root Context.
46
+ * NOTE: The package.json file defines the packaged #hyperformula module.
47
+ */
48
+ // TODO(burdon): Move graph into separate plugin; isolate HF deps.
49
+ export class ComputeGraphRegistry extends Resource {
50
+ private readonly _graphs = new Map<SpaceId, ComputeGraph>();
51
+
52
+ private readonly _options: ComputeGraphOptions;
53
+
54
+ constructor(options: ComputeGraphOptions = { plugins: defaultPlugins }) {
55
+ super();
56
+ this._options = defaultsDeep({}, options, defaultOptions);
57
+ this._options.plugins?.forEach(({ plugin, translations }) => {
58
+ HyperFormula.registerFunctionPlugin(plugin, translations);
59
+ });
60
+ }
61
+
62
+ getGraph(spaceId: SpaceId): ComputeGraph | undefined {
63
+ return this._graphs.get(spaceId);
64
+ }
65
+
66
+ getOrCreateGraph(space: Space): ComputeGraph {
67
+ let graph = this._graphs.get(space.id);
68
+ if (!graph) {
69
+ log('create graph', { space: space.id });
70
+ graph = this.createGraph(space);
71
+ }
72
+
73
+ return graph;
74
+ }
75
+
76
+ createGraph(space: Space): ComputeGraph {
77
+ invariant(!this._graphs.has(space.id), `ComputeGraph already exists for space: ${space.id}`);
78
+ const hf = HyperFormula.buildEmpty(this._options);
79
+ const graph = new ComputeGraph(hf, space, this._options);
80
+ this._graphs.set(space.id, graph);
81
+ return graph;
82
+ }
83
+
84
+ protected override async _close() {
85
+ for (const graph of this._graphs.values()) {
86
+ await graph.close();
87
+ }
88
+ this._graphs.clear();
89
+ }
90
+ }
@@ -13,7 +13,7 @@ import { Toolbar, Button, Input } from '@dxos/react-ui';
13
13
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
14
14
  import { withTheme } from '@dxos/storybook-utils';
15
15
 
16
- import { testPlugins } from './testing';
16
+ import { testFunctionPlugins } from './testing';
17
17
  import { createSheet } from '../defs';
18
18
  import { useComputeGraph, useSheetModel } from '../hooks';
19
19
  import { withComputeGraphDecorator } from '../testing';
@@ -84,7 +84,7 @@ export default {
84
84
  title: 'plugin-sheet/functions',
85
85
  decorators: [
86
86
  withClientProvider({ types: [FunctionType, SheetType], createIdentity: true, createSpace: true }),
87
- withComputeGraphDecorator({ plugins: testPlugins }),
87
+ withComputeGraphDecorator({ plugins: testFunctionPlugins }),
88
88
  withTheme,
89
89
  ],
90
90
  render: (args: any) => <Story {...args} />,
@@ -6,78 +6,49 @@ import { type CellValue } from 'hyperformula';
6
6
  import { afterEach, beforeEach, describe, expect, test } from 'vitest';
7
7
 
8
8
  import { Trigger } from '@dxos/async';
9
- import { Client } from '@dxos/client';
10
9
  import { create, fullyQualifiedId } from '@dxos/client/echo';
11
- import { Context } from '@dxos/context';
12
- import { type S } from '@dxos/echo-schema';
13
10
  import { FunctionType } from '@dxos/plugin-script/types';
14
11
 
15
- import { ComputeGraphRegistry } from './compute-graph';
16
- import { testPlugins } from './testing';
17
- import { addressFromA1Notation, createSheet } from '../defs';
18
- import { SheetModel } from '../model';
19
- import { type CellScalarValue } from '../types';
12
+ import { DetailedCellError } from '#hyperformula';
13
+ import { TestBuilder } from './testing';
20
14
 
21
- /**
22
- * NOTE: Browser test required for hyperformula due to raw translation files.
23
- */
24
- describe('compute graph', () => {
25
- let ctx: Context;
26
- beforeEach(() => {
27
- ctx = new Context();
15
+ describe('ComputeGraph', () => {
16
+ let testBuilder: TestBuilder;
17
+ beforeEach(async () => {
18
+ testBuilder = new TestBuilder({ types: [FunctionType] });
19
+ await testBuilder.open();
28
20
  });
29
21
  afterEach(async () => {
30
- await ctx.dispose();
22
+ await testBuilder.close();
31
23
  });
32
24
 
33
- // TODO(burdon): Replace with builder.
34
- const createModel = async (types?: S.Schema<any>[]) => {
35
- const client = new Client();
36
- if (types) {
37
- // TODO(burdon): Add to config.
38
- client.addTypes(types);
39
- }
40
- await client.initialize();
41
- await client.halo.createIdentity();
42
- const space = await client.spaces.create();
43
- ctx.onDispose(() => client.destroy());
44
-
45
- const registry = new ComputeGraphRegistry({ plugins: testPlugins });
46
- await registry.open();
47
- ctx.onDispose(() => registry.close());
48
-
49
- const graph = await registry.createGraph(space);
50
-
51
- const sheet = createSheet({ rows: 5, columns: 5 });
52
- const model = new SheetModel(graph, sheet);
53
- await model.open();
54
-
55
- return { space, graph, model };
56
- };
57
-
58
25
  test('map functions', async () => {
59
- const { space, graph } = await createModel([FunctionType]);
26
+ const space = await testBuilder.client.spaces.create();
27
+ const graph = testBuilder.registry.createGraph(space);
28
+ await graph.open();
60
29
 
61
30
  // Create script.
62
31
  const trigger = new Trigger();
63
32
  graph.update.once(() => trigger.wake());
64
- const fn = space.db.add(create(FunctionType, { version: 1, binding: 'TEST' }));
33
+ const functionObject = space.db.add(create(FunctionType, { version: 1, binding: 'TEST' }));
65
34
  await trigger.wait();
66
- expect(graph.getFunctions({ echo: true })).to.toHaveLength(1);
35
+ const functions = graph.getFunctions({ echo: true });
36
+ expect(functions).to.toHaveLength(1);
67
37
 
68
38
  const id = graph.mapFunctionBindingToId('TEST()');
69
- expect(id).to.eq(`${fullyQualifiedId(fn)}()`);
39
+ expect(id).to.eq(`${fullyQualifiedId(functionObject)}()`);
70
40
  });
71
41
 
72
42
  test('cross-node references', async () => {
73
- const { graph } = await createModel();
43
+ const space = await testBuilder.client.spaces.create();
44
+ const graph = testBuilder.registry.createGraph(space);
74
45
 
75
46
  // Create nodes.
76
- const node1 = await graph.getOrCreateNode('node-1');
77
- const node2 = await graph.getOrCreateNode('node-2');
47
+ const node1 = await graph.getOrCreateNode('node-1').open();
48
+ const node2 = await graph.getOrCreateNode('node-2').open();
49
+ expect(graph.hf.getSheetNames()).to.toHaveLength(2);
78
50
 
79
51
  {
80
- expect(graph.hf.getSheetNames()).to.toHaveLength(3);
81
52
  node1.graph.hf.setCellContents({ sheet: node1.sheetId, row: 0, col: 0 }, [[100, 200, 300, '=SUM(A1:C1)']]);
82
53
  node2.graph.hf.setCellContents({ sheet: node2.sheetId, row: 0, col: 0 }, "='node-1'!D1");
83
54
  const value1 = node1.graph.hf.getCellValue({ sheet: node1.sheetId, col: 3, row: 0 });
@@ -101,27 +72,16 @@ describe('compute graph', () => {
101
72
  }
102
73
  });
103
74
 
104
- test('async function', async () => {
105
- const { graph, model } = await createModel();
106
-
107
- // Triggers function.
108
- model.setValue(addressFromA1Notation('A1'), '=TEST()');
109
- const trigger = new Trigger<CellScalarValue>();
110
- model.update.once(({ type }) => {
111
- if (type === 'valuesUpdated') {
112
- const value = model.getValue(addressFromA1Notation('A1'));
113
- trigger.wake(value);
114
- }
115
- });
116
-
117
- // Get initial value (null).
118
- const v1 = model.getValue(addressFromA1Notation('A1'));
119
- expect(v1).to.be.null;
120
- expect(graph.context.info.invocations.TEST).to.eq(undefined);
121
-
122
- // Wait until async update triggered.
123
- const v2 = await trigger.wait();
124
- expect(v2).not.to.be.null;
125
- expect(graph.context.info.invocations.TEST).to.eq(1);
75
+ // TODO(burdon): Dynamically load node/model based on dependencies.
76
+ // - Create dependency then close model.
77
+ test('dynamic loading', async () => {
78
+ const space = await testBuilder.client.spaces.create();
79
+ const graph = testBuilder.registry.createGraph(space);
80
+
81
+ const node1 = await graph.getOrCreateNode('node-1').open();
82
+ node1.graph.hf.setCellContents({ sheet: node1.sheetId, row: 0, col: 0 }, "='node-2'!A1");
83
+ const value1 = node1.graph.hf.getCellValue({ sheet: node1.sheetId, col: 0, row: 0 });
84
+ expect(value1).to.be.instanceof(DetailedCellError);
85
+ expect((value1 as DetailedCellError).type).to.eq('REF');
126
86
  });
127
87
  });