@dxos/plugin-sheet 0.6.12-main.15a606f → 0.6.12-main.568932b
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/dist/lib/browser/{SheetContainer-V4GCCZTX.mjs → SheetContainer-T2QWJOFD.mjs} +14 -13
- package/dist/lib/browser/SheetContainer-T2QWJOFD.mjs.map +7 -0
- package/dist/lib/browser/{chunk-T3NJFTD4.mjs → chunk-5ZMVZYGB.mjs} +18 -6
- package/dist/lib/browser/{chunk-T3NJFTD4.mjs.map → chunk-5ZMVZYGB.mjs.map} +3 -3
- package/dist/lib/browser/{chunk-6ZMQVB4Z.mjs → chunk-GSV5QNLD.mjs} +220 -177
- package/dist/lib/browser/chunk-GSV5QNLD.mjs.map +7 -0
- package/dist/lib/browser/{chunk-U2JHW3L6.mjs → chunk-ZL2V5UJR.mjs} +206 -53
- package/dist/lib/browser/chunk-ZL2V5UJR.mjs.map +7 -0
- package/dist/lib/browser/graph-M4IQ76QX.mjs +33 -0
- package/dist/lib/browser/index.mjs +45 -21
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types.mjs +1 -1
- package/dist/lib/node/{SheetContainer-3ZY7MPWJ.cjs → SheetContainer-PV5ET4UJ.cjs} +21 -20
- package/dist/lib/node/SheetContainer-PV5ET4UJ.cjs.map +7 -0
- package/dist/lib/node/{chunk-OTTD7FBK.cjs → chunk-2K53Z2TU.cjs} +224 -70
- package/dist/lib/node/chunk-2K53Z2TU.cjs.map +7 -0
- package/dist/lib/node/{chunk-DD6FIXWC.cjs → chunk-5XPK2V4A.cjs} +222 -175
- package/dist/lib/node/chunk-5XPK2V4A.cjs.map +7 -0
- package/dist/lib/node/{chunk-Q3HBHPRL.cjs → chunk-STAVQ2JE.cjs} +21 -9
- package/dist/lib/node/{chunk-Q3HBHPRL.cjs.map → chunk-STAVQ2JE.cjs.map} +3 -3
- package/dist/lib/node/graph-Q3N2X26H.cjs +55 -0
- package/dist/lib/node/graph-Q3N2X26H.cjs.map +7 -0
- package/dist/lib/node/index.cjs +51 -30
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types.cjs +8 -8
- package/dist/lib/node/types.cjs.map +1 -1
- package/dist/lib/node-esm/{SheetContainer-PXSJX6XK.mjs → SheetContainer-FOZD2WLT.mjs} +14 -13
- package/dist/lib/node-esm/SheetContainer-FOZD2WLT.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-BMNA27EX.mjs → chunk-2HAM45RC.mjs} +18 -6
- package/dist/lib/node-esm/{chunk-BMNA27EX.mjs.map → chunk-2HAM45RC.mjs.map} +3 -3
- package/dist/lib/node-esm/{chunk-D6KU5MI7.mjs → chunk-5WPZCXNS.mjs} +220 -177
- package/dist/lib/node-esm/chunk-5WPZCXNS.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-7HVSOTGA.mjs → chunk-QEUCIHIN.mjs} +206 -53
- package/dist/lib/node-esm/chunk-QEUCIHIN.mjs.map +7 -0
- package/dist/lib/node-esm/graph-SMPUMOV2.mjs +34 -0
- package/dist/lib/node-esm/index.mjs +45 -21
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/types.mjs +1 -1
- package/dist/types/src/SheetPlugin.d.ts.map +1 -1
- package/dist/types/src/components/CellEditor/CellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/CellEditor/extension.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/GridSheet.d.ts +3 -3
- package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts +1 -1
- package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts.map +1 -1
- package/dist/types/src/components/GridSheet/util.d.ts +12 -2
- package/dist/types/src/components/GridSheet/util.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/Sheet.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +1 -1
- package/dist/types/src/components/Sheet/sheet-context.d.ts +3 -3
- package/dist/types/src/components/Sheet/sheet-context.d.ts.map +1 -1
- package/dist/types/src/components/SheetContainer.d.ts +1 -1
- package/dist/types/src/components/SheetContainer.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/defs/types.d.ts.map +1 -1
- package/dist/types/src/defs/util.d.ts +1 -1
- package/dist/types/src/defs/util.d.ts.map +1 -1
- package/dist/types/src/extensions/compute.d.ts +3 -2
- package/dist/types/src/extensions/compute.d.ts.map +1 -1
- package/dist/types/src/extensions/compute.stories.d.ts.map +1 -1
- package/dist/types/src/graph/compute-graph-registry.d.ts +34 -0
- package/dist/types/src/graph/compute-graph-registry.d.ts.map +1 -0
- package/dist/types/src/graph/compute-graph.d.ts +17 -34
- package/dist/types/src/graph/compute-graph.d.ts.map +1 -1
- package/dist/types/src/graph/compute-graph.stories.d.ts.map +1 -1
- package/dist/types/src/graph/compute-graph.test.d.ts +2 -0
- package/dist/types/src/graph/compute-graph.test.d.ts.map +1 -0
- package/dist/types/src/graph/compute-node.d.ts +9 -2
- package/dist/types/src/graph/compute-node.d.ts.map +1 -1
- package/dist/types/src/graph/{async-function.d.ts → functions/async-function.d.ts} +13 -4
- package/dist/types/src/graph/functions/async-function.d.ts.map +1 -0
- package/dist/types/src/graph/functions/edge-function.d.ts +21 -0
- package/dist/types/src/graph/functions/edge-function.d.ts.map +1 -0
- package/dist/types/src/graph/functions/function-defs.d.ts.map +1 -0
- package/dist/types/src/graph/functions/index.d.ts +4 -0
- package/dist/types/src/graph/functions/index.d.ts.map +1 -0
- package/dist/types/src/graph/index.d.ts +2 -1
- package/dist/types/src/graph/index.d.ts.map +1 -1
- package/dist/types/src/graph/testing/index.d.ts +3 -0
- package/dist/types/src/graph/testing/index.d.ts.map +1 -0
- package/dist/types/src/graph/testing/test-builder.d.ts +15 -0
- package/dist/types/src/graph/testing/test-builder.d.ts.map +1 -0
- package/dist/types/src/graph/testing/test-plugin.d.ts +36 -0
- package/dist/types/src/graph/testing/test-plugin.d.ts.map +1 -0
- package/dist/types/src/hooks/useComputeGraph.d.ts.map +1 -1
- package/dist/types/src/hooks/useSheetModel.d.ts +2 -2
- package/dist/types/src/hooks/useSheetModel.d.ts.map +1 -1
- package/dist/types/src/model/sheet-model.d.ts +3 -3
- package/dist/types/src/model/sheet-model.d.ts.map +1 -1
- package/dist/types/src/model/sheet-model.test.d.ts +2 -0
- package/dist/types/src/model/sheet-model.test.d.ts.map +1 -0
- package/dist/types/src/testing/testing.d.ts +4 -5
- package/dist/types/src/testing/testing.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +4 -3
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +40 -39
- package/src/SheetPlugin.tsx +19 -15
- package/src/components/CellEditor/CellEditor.stories.tsx +2 -3
- package/src/components/CellEditor/extension.test.ts +0 -1
- package/src/components/CellEditor/extension.ts +4 -3
- package/src/components/GridSheet/GridSheet.stories.tsx +5 -4
- package/src/components/GridSheet/GridSheet.tsx +27 -9
- package/src/components/GridSheet/util.ts +93 -34
- package/src/components/Sheet/Sheet.stories.tsx +21 -20
- package/src/components/Sheet/Sheet.tsx +30 -14
- package/src/components/Sheet/sheet-context.tsx +4 -4
- package/src/components/SheetContainer.tsx +13 -15
- package/src/defs/types.ts +1 -0
- package/src/defs/util.ts +19 -3
- package/src/extensions/compute.stories.tsx +20 -20
- package/src/extensions/compute.ts +91 -42
- package/src/graph/compute-graph-registry.ts +90 -0
- package/src/graph/compute-graph.stories.tsx +4 -3
- package/src/graph/compute-graph.test.ts +87 -0
- package/src/graph/compute-graph.ts +73 -121
- package/src/graph/compute-node.ts +17 -5
- package/src/graph/{async-function.ts → functions/async-function.ts} +23 -15
- package/src/graph/{edge-function.ts → functions/edge-function.ts} +14 -13
- package/src/graph/functions/index.ts +7 -0
- package/src/graph/hyperformula.test.ts +1 -2
- package/src/graph/index.ts +2 -1
- package/src/graph/testing/index.ts +6 -0
- package/src/graph/testing/test-builder.ts +54 -0
- package/src/graph/{custom-function.ts → testing/test-plugin.ts} +43 -9
- package/src/hooks/hooks.stories.tsx +3 -3
- package/src/hooks/useComputeGraph.ts +9 -1
- package/src/hooks/useSheetModel.ts +4 -7
- package/src/model/sheet-model.test.ts +57 -0
- package/src/model/sheet-model.ts +47 -30
- package/src/testing/testing.tsx +17 -15
- package/src/types.ts +7 -7
- package/dist/lib/browser/SheetContainer-V4GCCZTX.mjs.map +0 -7
- package/dist/lib/browser/chunk-6ZMQVB4Z.mjs.map +0 -7
- package/dist/lib/browser/chunk-U2JHW3L6.mjs.map +0 -7
- package/dist/lib/browser/graph-T27BOBOV.mjs +0 -21
- package/dist/lib/node/SheetContainer-3ZY7MPWJ.cjs.map +0 -7
- package/dist/lib/node/chunk-DD6FIXWC.cjs.map +0 -7
- package/dist/lib/node/chunk-OTTD7FBK.cjs.map +0 -7
- package/dist/lib/node/graph-SPKGX7W4.cjs +0 -43
- package/dist/lib/node/graph-SPKGX7W4.cjs.map +0 -7
- package/dist/lib/node-esm/SheetContainer-PXSJX6XK.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-7HVSOTGA.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-D6KU5MI7.mjs.map +0 -7
- package/dist/lib/node-esm/graph-U67IO4UC.mjs +0 -22
- package/dist/types/src/graph/async-function.d.ts.map +0 -1
- package/dist/types/src/graph/compute-graph.browser.test.d.ts +0 -2
- package/dist/types/src/graph/compute-graph.browser.test.d.ts.map +0 -1
- package/dist/types/src/graph/custom-function.d.ts +0 -21
- package/dist/types/src/graph/custom-function.d.ts.map +0 -1
- package/dist/types/src/graph/edge-function.d.ts +0 -20
- package/dist/types/src/graph/edge-function.d.ts.map +0 -1
- package/dist/types/src/graph/function-defs.d.ts.map +0 -1
- package/src/graph/compute-graph.browser.test.ts +0 -104
- /package/dist/lib/browser/{graph-T27BOBOV.mjs.map → graph-M4IQ76QX.mjs.map} +0 -0
- /package/dist/lib/node-esm/{graph-U67IO4UC.mjs.map → graph-SMPUMOV2.mjs.map} +0 -0
- /package/dist/types/src/graph/{function-defs.d.ts → functions/function-defs.d.ts} +0 -0
- /package/src/graph/{function-defs.ts → functions/function-defs.ts} +0 -0
|
@@ -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,9 +13,10 @@ 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 { testFunctionPlugins } from './testing';
|
|
16
17
|
import { createSheet } from '../defs';
|
|
17
18
|
import { useComputeGraph, useSheetModel } from '../hooks';
|
|
18
|
-
import {
|
|
19
|
+
import { withComputeGraphDecorator } from '../testing';
|
|
19
20
|
import { SheetType } from '../types';
|
|
20
21
|
|
|
21
22
|
const FUNCTION_NAME = 'TEST';
|
|
@@ -26,7 +27,7 @@ const Story = () => {
|
|
|
26
27
|
const [sheet, setSheet] = useState<SheetType>();
|
|
27
28
|
const [text, setText] = useState(`${FUNCTION_NAME}(100)`);
|
|
28
29
|
const [result, setResult] = useState<any>();
|
|
29
|
-
const model = useSheetModel(
|
|
30
|
+
const model = useSheetModel(graph, sheet);
|
|
30
31
|
useEffect(() => {
|
|
31
32
|
if (space) {
|
|
32
33
|
const sheet = space.db.add(createSheet());
|
|
@@ -83,7 +84,7 @@ export default {
|
|
|
83
84
|
title: 'plugin-sheet/functions',
|
|
84
85
|
decorators: [
|
|
85
86
|
withClientProvider({ types: [FunctionType, SheetType], createIdentity: true, createSpace: true }),
|
|
86
|
-
|
|
87
|
+
withComputeGraphDecorator({ plugins: testFunctionPlugins }),
|
|
87
88
|
withTheme,
|
|
88
89
|
],
|
|
89
90
|
render: (args: any) => <Story {...args} />,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type CellValue } from 'hyperformula';
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { Trigger } from '@dxos/async';
|
|
9
|
+
import { create, fullyQualifiedId } from '@dxos/client/echo';
|
|
10
|
+
import { FunctionType } from '@dxos/plugin-script/types';
|
|
11
|
+
|
|
12
|
+
import { DetailedCellError } from '#hyperformula';
|
|
13
|
+
import { TestBuilder } from './testing';
|
|
14
|
+
|
|
15
|
+
describe('ComputeGraph', () => {
|
|
16
|
+
let testBuilder: TestBuilder;
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
testBuilder = new TestBuilder({ types: [FunctionType] });
|
|
19
|
+
await testBuilder.open();
|
|
20
|
+
});
|
|
21
|
+
afterEach(async () => {
|
|
22
|
+
await testBuilder.close();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('map functions', async () => {
|
|
26
|
+
const space = await testBuilder.client.spaces.create();
|
|
27
|
+
const graph = testBuilder.registry.createGraph(space);
|
|
28
|
+
await graph.open();
|
|
29
|
+
|
|
30
|
+
// Create script.
|
|
31
|
+
const trigger = new Trigger();
|
|
32
|
+
graph.update.once(() => trigger.wake());
|
|
33
|
+
const functionObject = space.db.add(create(FunctionType, { version: 1, binding: 'TEST' }));
|
|
34
|
+
await trigger.wait();
|
|
35
|
+
const functions = graph.getFunctions({ echo: true });
|
|
36
|
+
expect(functions).to.toHaveLength(1);
|
|
37
|
+
|
|
38
|
+
const id = graph.mapFunctionBindingToId('TEST()');
|
|
39
|
+
expect(id).to.eq(`${fullyQualifiedId(functionObject)}()`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('cross-node references', async () => {
|
|
43
|
+
const space = await testBuilder.client.spaces.create();
|
|
44
|
+
const graph = testBuilder.registry.createGraph(space);
|
|
45
|
+
|
|
46
|
+
// Create nodes.
|
|
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);
|
|
50
|
+
|
|
51
|
+
{
|
|
52
|
+
node1.graph.hf.setCellContents({ sheet: node1.sheetId, row: 0, col: 0 }, [[100, 200, 300, '=SUM(A1:C1)']]);
|
|
53
|
+
node2.graph.hf.setCellContents({ sheet: node2.sheetId, row: 0, col: 0 }, "='node-1'!D1");
|
|
54
|
+
const value1 = node1.graph.hf.getCellValue({ sheet: node1.sheetId, col: 3, row: 0 });
|
|
55
|
+
const value2 = node2.graph.hf.getCellValue({ sheet: node2.sheetId, col: 0, row: 0 });
|
|
56
|
+
expect(value1).to.eq(value2);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Get updated event.
|
|
60
|
+
const trigger = new Trigger<CellValue>();
|
|
61
|
+
node2.update.on(({ change }) => {
|
|
62
|
+
const value = node2.graph.hf.getCellValue({ sheet: node2.sheetId, col: 0, row: 0 });
|
|
63
|
+
expect(value).to.eq(change?.newValue);
|
|
64
|
+
trigger.wake(value);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
{
|
|
68
|
+
node1.graph.hf.setCellContents({ sheet: node1.sheetId, row: 0, col: 0 }, 400);
|
|
69
|
+
const value1 = node1.graph.hf.getCellValue({ sheet: node1.sheetId, col: 3, row: 0 });
|
|
70
|
+
const value2 = await trigger.wait();
|
|
71
|
+
expect(value1).to.eq(value2);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
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');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -2,112 +2,47 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type
|
|
6
|
-
import { type ConfigParams } from 'hyperformula/typings/ConfigParams';
|
|
7
|
-
import { type FunctionTranslationsPackage } from 'hyperformula/typings/interpreter';
|
|
5
|
+
import { type Listeners } from 'hyperformula/typings/Emitter';
|
|
8
6
|
|
|
9
7
|
import { Event } from '@dxos/async';
|
|
10
|
-
import { type
|
|
8
|
+
import { type Space, Filter, fullyQualifiedId } from '@dxos/client/echo';
|
|
11
9
|
import { Resource } from '@dxos/context';
|
|
10
|
+
import { getTypename } from '@dxos/echo-schema';
|
|
12
11
|
import { invariant } from '@dxos/invariant';
|
|
13
12
|
import { PublicKey } from '@dxos/keys';
|
|
14
13
|
import { log } from '@dxos/log';
|
|
15
14
|
import { FunctionType } from '@dxos/plugin-script/types';
|
|
16
15
|
import { nonNullable } from '@dxos/util';
|
|
17
16
|
|
|
18
|
-
import { HyperFormula } from '#hyperformula';
|
|
19
|
-
import { FunctionContext, type FunctionContextOptions } from './async-function';
|
|
17
|
+
import { ExportedCellChange, type HyperFormula } from '#hyperformula';
|
|
20
18
|
import { ComputeNode } from './compute-node';
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
import {
|
|
20
|
+
defaultFunctions,
|
|
21
|
+
FunctionContext,
|
|
22
|
+
type FunctionContextOptions,
|
|
23
|
+
type FunctionDefinition,
|
|
24
|
+
EDGE_FUNCTION_NAME,
|
|
25
|
+
} from './functions';
|
|
27
26
|
|
|
28
27
|
// TODO(wittjosiah): Factor out.
|
|
29
|
-
const OBJECT_ID_LENGTH = 60; // 33 (space id) +
|
|
28
|
+
const OBJECT_ID_LENGTH = 60; // 33 (space id) + 1 (separator) + 26 (object id).
|
|
30
29
|
|
|
31
|
-
// TODO(burdon):
|
|
32
|
-
|
|
30
|
+
// TODO(burdon): Factory.
|
|
31
|
+
// export type ComputeNodeGenerator = <T>(obj: T) => ComputeNode;
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
plugin: FunctionPluginDefinition;
|
|
36
|
-
translations: FunctionTranslationsPackage;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export type ComputeGraphOptions = {
|
|
40
|
-
plugins?: ComputeGraphPlugin[];
|
|
41
|
-
} & Partial<FunctionContextOptions> &
|
|
42
|
-
Partial<ConfigParams>;
|
|
43
|
-
|
|
44
|
-
export const defaultOptions: ComputeGraphOptions = {
|
|
45
|
-
licenseKey: 'gpl-v3',
|
|
46
|
-
plugins: [
|
|
47
|
-
{
|
|
48
|
-
plugin: EdgeFunctionPlugin,
|
|
49
|
-
translations: EdgeFunctionPluginTranslations,
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
};
|
|
33
|
+
type ObjectRef = { type: string; id: string };
|
|
53
34
|
|
|
54
35
|
/**
|
|
55
36
|
* Marker for sheets that are managed by an ECHO object.
|
|
37
|
+
* Sheet ID: `dxos.org/type/SheetType@1234`
|
|
56
38
|
*/
|
|
57
|
-
const
|
|
58
|
-
export const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* NOTE: Async imports to decouple hyperformula deps.
|
|
64
|
-
*/
|
|
65
|
-
export const createComputeGraphRegistry = (options: Partial<FunctionContextOptions> = {}) => {
|
|
66
|
-
return new ComputeGraphRegistry({
|
|
67
|
-
...defaultOptions,
|
|
68
|
-
...options,
|
|
69
|
-
});
|
|
39
|
+
export const createSheetName = ({ type, id }: ObjectRef) => `${type}@${id}`;
|
|
40
|
+
export const parseSheetName = (name: string): Partial<ObjectRef> => {
|
|
41
|
+
const [type, id] = name.split('@');
|
|
42
|
+
return id ? { type, id } : { id: type };
|
|
70
43
|
};
|
|
71
44
|
|
|
72
|
-
|
|
73
|
-
* Manages a collection of ComputeGraph instances for each space.
|
|
74
|
-
*
|
|
75
|
-
* [ComputePlugin] => [ComputeGraphRegistry] => [ComputeGraph(Space)] => [ComputeNode(Object)]
|
|
76
|
-
*/
|
|
77
|
-
// TODO(burdon): Move graph into separate plugin; isolate HF deps.
|
|
78
|
-
export class ComputeGraphRegistry extends Resource {
|
|
79
|
-
private readonly _registry = new Map<SpaceId, ComputeGraph>();
|
|
80
|
-
|
|
81
|
-
constructor(private readonly _options: ComputeGraphOptions = defaultOptions) {
|
|
82
|
-
super();
|
|
83
|
-
this._options.plugins?.forEach(({ plugin, translations }) => {
|
|
84
|
-
HyperFormula.registerFunctionPlugin(plugin, translations);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
getGraph(spaceId: SpaceId): ComputeGraph | undefined {
|
|
89
|
-
return this._registry.get(spaceId);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async getOrCreateGraph(space: Space): Promise<ComputeGraph> {
|
|
93
|
-
let graph = this.getGraph(space.id);
|
|
94
|
-
if (!graph) {
|
|
95
|
-
log.info('create graph', { space: space.id });
|
|
96
|
-
graph = await this.createGraph(space);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return graph;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async createGraph(space: Space): Promise<ComputeGraph> {
|
|
103
|
-
invariant(!this._registry.has(space.id), `ComputeGraph already exists for space: ${space.id}`);
|
|
104
|
-
const hf = HyperFormula.buildEmpty(this._options);
|
|
105
|
-
const graph = new ComputeGraph(hf, space, this._options);
|
|
106
|
-
await graph.open(this._ctx);
|
|
107
|
-
this._registry.set(space.id, graph);
|
|
108
|
-
return graph;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
45
|
+
export type ComputeGraphEvent = 'functionsUpdated';
|
|
111
46
|
|
|
112
47
|
/**
|
|
113
48
|
* Per-space compute and dependency graph.
|
|
@@ -115,7 +50,6 @@ export class ComputeGraphRegistry extends Resource {
|
|
|
115
50
|
* Manages the set of custom functions.
|
|
116
51
|
* HyperFormula manages the dependency graph.
|
|
117
52
|
*/
|
|
118
|
-
// TODO(burdon): Tests.
|
|
119
53
|
export class ComputeGraph extends Resource {
|
|
120
54
|
public readonly id = `graph-${PublicKey.random().truncate()}`;
|
|
121
55
|
|
|
@@ -123,15 +57,12 @@ export class ComputeGraph extends Resource {
|
|
|
123
57
|
private readonly _nodes = new Map<number, ComputeNode>();
|
|
124
58
|
|
|
125
59
|
// Cached function objects.
|
|
126
|
-
private
|
|
60
|
+
private _remoteFunctions: FunctionType[] = [];
|
|
127
61
|
|
|
128
|
-
|
|
129
|
-
public readonly context = new FunctionContext(this._hf, this._space, this.refresh.bind(this), this._options);
|
|
62
|
+
public readonly update = new Event<{ type: ComputeGraphEvent }>();
|
|
130
63
|
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
// TODO(burdon): Event propagation.
|
|
134
|
-
public readonly update = new Event();
|
|
64
|
+
// The context is passed to all functions.
|
|
65
|
+
public readonly context = new FunctionContext(this._hf, this._space, this._options);
|
|
135
66
|
|
|
136
67
|
constructor(
|
|
137
68
|
private readonly _hf: HyperFormula,
|
|
@@ -139,28 +70,38 @@ export class ComputeGraph extends Resource {
|
|
|
139
70
|
private readonly _options?: Partial<FunctionContextOptions>,
|
|
140
71
|
) {
|
|
141
72
|
super();
|
|
142
|
-
|
|
143
73
|
this._hf.updateConfig({ context: this.context });
|
|
74
|
+
// TODO(burdon): If debounce then aggregate changes.
|
|
75
|
+
const onValuesUpdate: Listeners['valuesUpdated'] = (changes) => {
|
|
76
|
+
for (const change of changes) {
|
|
77
|
+
if (change instanceof ExportedCellChange) {
|
|
78
|
+
const { sheet } = change;
|
|
79
|
+
const node = this._nodes.get(sheet);
|
|
80
|
+
if (node) {
|
|
81
|
+
node.update.emit({ type: 'valuesUpdated', change });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
this._hf.on('valuesUpdated', onValuesUpdate);
|
|
88
|
+
this._ctx.onDispose(() => this._hf.off('valuesUpdated', onValuesUpdate));
|
|
144
89
|
}
|
|
145
90
|
|
|
146
|
-
// TODO(burdon): Remove.
|
|
147
91
|
get hf() {
|
|
148
92
|
return this._hf;
|
|
149
93
|
}
|
|
150
94
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
getFunctions({ standard = true, echo = true }: { standard?: boolean; echo?: boolean } = {}): FunctionDefinition[] {
|
|
95
|
+
getFunctions(
|
|
96
|
+
{ standard, echo }: { standard?: boolean; echo?: boolean } = { standard: true, echo: true },
|
|
97
|
+
): FunctionDefinition[] {
|
|
157
98
|
return [
|
|
158
99
|
...(standard
|
|
159
100
|
? this._hf
|
|
160
101
|
.getRegisteredFunctionNames()
|
|
161
102
|
.map((name) => defaultFunctions.find((fn) => fn.name === name) ?? { name })
|
|
162
103
|
: []),
|
|
163
|
-
...(echo ? this.
|
|
104
|
+
...(echo ? this._remoteFunctions.map((fn) => ({ name: fn.binding! })) : []),
|
|
164
105
|
];
|
|
165
106
|
}
|
|
166
107
|
|
|
@@ -175,15 +116,13 @@ export class ComputeGraph extends Resource {
|
|
|
175
116
|
getOrCreateNode(name: string): ComputeNode {
|
|
176
117
|
invariant(name.length);
|
|
177
118
|
if (!this._hf.doesSheetExist(name)) {
|
|
178
|
-
log.info('created node', { space: this._space?.id, name });
|
|
119
|
+
log.info('created node', { space: this._space?.id, sheet: name });
|
|
179
120
|
this._hf.addSheet(name);
|
|
180
|
-
this.update.emit();
|
|
181
121
|
}
|
|
182
122
|
|
|
183
123
|
const sheetId = this._hf.getSheetId(name);
|
|
184
124
|
invariant(sheetId !== undefined);
|
|
185
125
|
|
|
186
|
-
// TODO(burdon): Chain context?
|
|
187
126
|
const node = new ComputeNode(this, sheetId);
|
|
188
127
|
this._nodes.set(sheetId, node);
|
|
189
128
|
return node;
|
|
@@ -196,22 +135,27 @@ export class ComputeGraph extends Resource {
|
|
|
196
135
|
mapFormulaToNative(formula: string): string {
|
|
197
136
|
return (
|
|
198
137
|
formula
|
|
199
|
-
//
|
|
138
|
+
//
|
|
139
|
+
// Map cross-sheet references by name onto sheet stored by ECHO object/model.
|
|
140
|
+
// Example: "Test Sheet"!A0 => "dxos.org/type/SheetType@1234"!A0
|
|
200
141
|
// https://hyperformula.handsontable.com/guide/cell-references.html#cell-references
|
|
142
|
+
//
|
|
201
143
|
.replace(/['"]?([ \w]+)['"]?!/, (_match, name) => {
|
|
202
144
|
if (name) {
|
|
203
|
-
// TODO(burdon):
|
|
145
|
+
// TODO(burdon): Cache map.
|
|
204
146
|
const objects = this._hf
|
|
205
147
|
.getSheetNames()
|
|
206
148
|
.map((name) => {
|
|
207
|
-
const id =
|
|
208
|
-
return id ? this._space?.db.getObjectById(id) : undefined;
|
|
149
|
+
const { type, id } = parseSheetName(name);
|
|
150
|
+
return type && id ? this._space?.db.getObjectById(id) : undefined;
|
|
209
151
|
})
|
|
210
152
|
.filter(nonNullable);
|
|
211
153
|
|
|
212
154
|
for (const obj of objects) {
|
|
213
|
-
if (obj.name === name
|
|
214
|
-
|
|
155
|
+
if (obj.name === name) {
|
|
156
|
+
const type = getTypename(obj)!;
|
|
157
|
+
// NOTE: Names must be single quoted.
|
|
158
|
+
return `'${createSheetName({ type, id: obj.id })}'!`;
|
|
215
159
|
}
|
|
216
160
|
}
|
|
217
161
|
}
|
|
@@ -219,17 +163,19 @@ export class ComputeGraph extends Resource {
|
|
|
219
163
|
return `${name}!`;
|
|
220
164
|
})
|
|
221
165
|
|
|
222
|
-
//
|
|
166
|
+
//
|
|
167
|
+
// Map remote function references (i.e., to remote DX function invocation).
|
|
168
|
+
//
|
|
223
169
|
.replace(/(\w+)\((.*)\)/g, (match, binding, args) => {
|
|
224
|
-
const fn = this.
|
|
170
|
+
const fn = this._remoteFunctions.find((fn) => fn.binding === binding);
|
|
225
171
|
if (!fn) {
|
|
226
172
|
return match;
|
|
227
173
|
}
|
|
228
174
|
|
|
229
175
|
if (args.trim() === '') {
|
|
230
|
-
return `${
|
|
176
|
+
return `${EDGE_FUNCTION_NAME}("${binding}")`;
|
|
231
177
|
} else {
|
|
232
|
-
return `${
|
|
178
|
+
return `${EDGE_FUNCTION_NAME}("${binding}", ${args})`;
|
|
233
179
|
}
|
|
234
180
|
})
|
|
235
181
|
);
|
|
@@ -241,11 +187,11 @@ export class ComputeGraph extends Resource {
|
|
|
241
187
|
*/
|
|
242
188
|
mapFunctionBindingToId(formula: string) {
|
|
243
189
|
return formula.replace(/(\w+)\((.*)\)/g, (match, binding, args) => {
|
|
244
|
-
if (binding ===
|
|
190
|
+
if (binding === EDGE_FUNCTION_NAME || defaultFunctions.find((fn) => fn.name === binding)) {
|
|
245
191
|
return match;
|
|
246
192
|
}
|
|
247
193
|
|
|
248
|
-
const fn = this.
|
|
194
|
+
const fn = this._remoteFunctions.find((fn) => fn.binding === binding);
|
|
249
195
|
if (fn) {
|
|
250
196
|
const id = fullyQualifiedId(fn);
|
|
251
197
|
return `${id}(${args})`;
|
|
@@ -266,7 +212,7 @@ export class ComputeGraph extends Resource {
|
|
|
266
212
|
return match;
|
|
267
213
|
}
|
|
268
214
|
|
|
269
|
-
const fn = this.
|
|
215
|
+
const fn = this._remoteFunctions.find((fn) => fullyQualifiedId(fn) === id);
|
|
270
216
|
if (fn?.binding) {
|
|
271
217
|
return `${fn.binding}(${args})`;
|
|
272
218
|
} else {
|
|
@@ -280,11 +226,17 @@ export class ComputeGraph extends Resource {
|
|
|
280
226
|
// Subscribe to remote function definitions.
|
|
281
227
|
const query = this._space.db.query(Filter.schema(FunctionType));
|
|
282
228
|
const unsubscribe = query.subscribe(({ objects }) => {
|
|
283
|
-
this.
|
|
284
|
-
this.update.emit();
|
|
229
|
+
this._remoteFunctions = objects.filter(({ binding }) => binding);
|
|
230
|
+
this.update.emit({ type: 'functionsUpdated' });
|
|
285
231
|
});
|
|
286
232
|
|
|
287
233
|
this._ctx.onDispose(unsubscribe);
|
|
288
234
|
}
|
|
289
235
|
}
|
|
236
|
+
|
|
237
|
+
protected override async _close() {
|
|
238
|
+
for (const node of this._nodes.values()) {
|
|
239
|
+
await node.close();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
290
242
|
}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Listeners } from 'hyperformula/typings/Emitter';
|
|
6
|
+
import { type ExportedCellChange } from 'hyperformula/typings/Exporter';
|
|
7
|
+
|
|
5
8
|
import { Event } from '@dxos/async';
|
|
6
9
|
import { Resource } from '@dxos/context';
|
|
7
10
|
|
|
@@ -10,13 +13,17 @@ import { type ComputeGraph } from './compute-graph';
|
|
|
10
13
|
import { type CellAddress } from '../defs';
|
|
11
14
|
import { type CellScalarValue } from '../types';
|
|
12
15
|
|
|
16
|
+
export type ComputeNodeEvent = {
|
|
17
|
+
type: keyof Listeners;
|
|
18
|
+
change?: ExportedCellChange;
|
|
19
|
+
};
|
|
20
|
+
|
|
13
21
|
/**
|
|
14
22
|
* Individual "sheet" (typically corresponds to an ECHO object).
|
|
15
23
|
*/
|
|
16
24
|
// TODO(burdon): Factor out common HF wrapper from from SheetModel.
|
|
17
25
|
export class ComputeNode extends Resource {
|
|
18
|
-
|
|
19
|
-
public readonly update = new Event();
|
|
26
|
+
public readonly update = new Event<ComputeNodeEvent>();
|
|
20
27
|
|
|
21
28
|
constructor(
|
|
22
29
|
private readonly _graph: ComputeGraph,
|
|
@@ -25,13 +32,12 @@ export class ComputeNode extends Resource {
|
|
|
25
32
|
super();
|
|
26
33
|
}
|
|
27
34
|
|
|
28
|
-
// TODO(burdon): Remove?
|
|
29
35
|
get graph() {
|
|
30
36
|
return this._graph;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
clear() {
|
|
40
|
+
this._graph.hf.clearSheet(this.sheetId);
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
getValue(cell: CellAddress): CellScalarValue {
|
|
@@ -48,4 +54,10 @@ export class ComputeNode extends Resource {
|
|
|
48
54
|
typeof value === 'string' && value.charAt(0) === '=' ? this._graph.mapFormulaToNative(value) : value;
|
|
49
55
|
this._graph.hf.setCellContents({ sheet: this.sheetId, row: cell.row, col: cell.col }, [[mappedValue]]);
|
|
50
56
|
}
|
|
57
|
+
|
|
58
|
+
// TODO(burdon): Load data into sheet.
|
|
59
|
+
protected override async _open() {
|
|
60
|
+
// const unsubscribe = this._graph.update.on(this.update.emit);
|
|
61
|
+
// this._ctx.onDispose(unsubscribe);
|
|
62
|
+
}
|
|
51
63
|
}
|