@dxos/plugin-sheet 0.6.12-main.ed7cda7 → 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.
- package/dist/lib/browser/{SheetContainer-V4GCCZTX.mjs → SheetContainer-LG77O4RM.mjs} +14 -13
- package/dist/lib/browser/SheetContainer-LG77O4RM.mjs.map +7 -0
- package/dist/lib/browser/{chunk-U2JHW3L6.mjs → chunk-CHQAW4F4.mjs} +206 -53
- package/dist/lib/browser/chunk-CHQAW4F4.mjs.map +7 -0
- 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-T3NJFTD4.mjs → chunk-WZMOZKQZ.mjs} +2 -2
- package/dist/lib/browser/{chunk-T3NJFTD4.mjs.map → chunk-WZMOZKQZ.mjs.map} +3 -3
- 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-OZ7DHH4L.cjs} +21 -20
- package/dist/lib/node/SheetContainer-OZ7DHH4L.cjs.map +7 -0
- package/dist/lib/node/{chunk-OTTD7FBK.cjs → chunk-5FTFZL5W.cjs} +224 -70
- package/dist/lib/node/chunk-5FTFZL5W.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-AOP42UAA.cjs} +5 -5
- package/dist/lib/node/{chunk-Q3HBHPRL.cjs.map → chunk-AOP42UAA.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-4XS2G25Z.mjs} +14 -13
- package/dist/lib/node-esm/SheetContainer-4XS2G25Z.mjs.map +7 -0
- 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-KK3XL37M.mjs} +206 -53
- package/dist/lib/node-esm/chunk-KK3XL37M.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-BMNA27EX.mjs → chunk-RR2AO4SM.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-BMNA27EX.mjs.map → chunk-RR2AO4SM.mjs.map} +3 -3
- 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/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 +4 -4
- 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 +59 -0
- package/src/model/sheet-model.ts +47 -30
- package/src/testing/testing.tsx +17 -15
- package/src/types.ts +3 -3
- 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
|
@@ -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
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type SimpleCellAddress } from 'hyperformula/typings/Cell';
|
|
5
6
|
import { type InterpreterState } from 'hyperformula/typings/interpreter/InterpreterState';
|
|
6
7
|
import { type InterpreterValue } from 'hyperformula/typings/interpreter/InterpreterValue';
|
|
7
8
|
import { type ProcedureAst } from 'hyperformula/typings/parser';
|
|
@@ -13,28 +14,33 @@ import { log } from '@dxos/log';
|
|
|
13
14
|
|
|
14
15
|
import { CellError, ErrorType, EmptyValue, FunctionPlugin, type HyperFormula } from '#hyperformula';
|
|
15
16
|
|
|
16
|
-
// TODO(burdon): API gateways
|
|
17
|
+
// TODO(burdon): Create API gateways:
|
|
17
18
|
// https://publicapis.io
|
|
18
19
|
// https://api-ninjas.com/api/cryptoprice
|
|
19
20
|
// https://developers.google.com/apis-explorer
|
|
20
21
|
// https://publicapis.io/coin-desk-api
|
|
21
22
|
|
|
22
|
-
// TODO(burdon): Create wrapper.
|
|
23
23
|
export type AsyncFunction = (...args: any) => Promise<InterpreterValue>;
|
|
24
24
|
|
|
25
|
+
export type FunctionUpdateEvent = {
|
|
26
|
+
name: string;
|
|
27
|
+
cell: SimpleCellAddress;
|
|
28
|
+
};
|
|
29
|
+
|
|
25
30
|
export type FunctionOptions = {
|
|
26
31
|
ttl?: number;
|
|
27
32
|
};
|
|
28
33
|
|
|
29
34
|
export type FunctionContextOptions = {
|
|
30
35
|
defaultTtl: number;
|
|
31
|
-
|
|
36
|
+
debounceDelay: number;
|
|
32
37
|
remoteFunctionUrl: string;
|
|
38
|
+
onUpdate?: (update: FunctionUpdateEvent) => void;
|
|
33
39
|
};
|
|
34
40
|
|
|
35
41
|
export const defaultFunctionContextOptions: FunctionContextOptions = {
|
|
36
42
|
defaultTtl: 5_000,
|
|
37
|
-
|
|
43
|
+
debounceDelay: 200,
|
|
38
44
|
remoteFunctionUrl: 'https://edge.dxos.workers.dev/functions', // TODO(burdon): Config.
|
|
39
45
|
};
|
|
40
46
|
|
|
@@ -66,24 +72,23 @@ export class FunctionContext {
|
|
|
66
72
|
private _invocations: Record<string, number> = {};
|
|
67
73
|
|
|
68
74
|
private readonly _options: FunctionContextOptions;
|
|
69
|
-
|
|
75
|
+
|
|
76
|
+
// Debounced update handler.
|
|
77
|
+
private readonly _onUpdate: (update: FunctionUpdateEvent) => void;
|
|
70
78
|
|
|
71
79
|
constructor(
|
|
72
80
|
private readonly _hf: HyperFormula,
|
|
73
81
|
private readonly _space: Space | undefined,
|
|
74
|
-
onUpdate: (context: FunctionContext) => void,
|
|
75
82
|
_options?: Partial<FunctionContextOptions>,
|
|
76
83
|
) {
|
|
77
84
|
this._options = defaultsDeep(_options ?? {}, defaultFunctionContextOptions);
|
|
78
|
-
this._onUpdate = debounce(() => {
|
|
79
|
-
// TODO(burdon): Better way to trigger recalculation?
|
|
80
|
-
// NOTE: rebuildAndRecalculate resets the undo history.
|
|
85
|
+
this._onUpdate = debounce((update) => {
|
|
86
|
+
// TODO(burdon): Better way to trigger recalculation? (NOTE: rebuildAndRecalculate resets the undo history.)
|
|
81
87
|
this._hf.resumeEvaluation();
|
|
82
|
-
onUpdate(
|
|
83
|
-
}, this._options.
|
|
88
|
+
this._options.onUpdate?.(update);
|
|
89
|
+
}, this._options.debounceDelay);
|
|
84
90
|
}
|
|
85
91
|
|
|
86
|
-
// TODO(burdon): Remove?
|
|
87
92
|
get space() {
|
|
88
93
|
return this._space;
|
|
89
94
|
}
|
|
@@ -136,7 +141,7 @@ export class FunctionContext {
|
|
|
136
141
|
const value = await cb(...args);
|
|
137
142
|
this._cache.set(invocationKey, { value, ts: Date.now() });
|
|
138
143
|
log('set', { cell, value });
|
|
139
|
-
this._onUpdate();
|
|
144
|
+
this._onUpdate({ name, cell });
|
|
140
145
|
} catch (err) {
|
|
141
146
|
// TODO(burdon): Show error to user.
|
|
142
147
|
log.warn('failed', { cell, err });
|
|
@@ -155,12 +160,15 @@ export class FunctionContext {
|
|
|
155
160
|
/**
|
|
156
161
|
* Base class for async functions.
|
|
157
162
|
*/
|
|
158
|
-
export class
|
|
163
|
+
export class AsyncFunctionPlugin extends FunctionPlugin {
|
|
159
164
|
get context() {
|
|
160
165
|
return this.config.context as FunctionContext;
|
|
161
166
|
}
|
|
162
167
|
|
|
163
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Immediately returns cached value then runs the async function.
|
|
170
|
+
*/
|
|
171
|
+
protected runAsyncFunction(ast: ProcedureAst, state: InterpreterState, cb: AsyncFunction, options?: FunctionOptions) {
|
|
164
172
|
const { procedureName } = ast;
|
|
165
173
|
const metadata = this.metadata(procedureName);
|
|
166
174
|
return this.runFunction(ast.args, state, metadata, (...args: any) => {
|
|
@@ -13,17 +13,19 @@ import { FunctionType } from '@dxos/plugin-script/types';
|
|
|
13
13
|
import { nonNullable } from '@dxos/util';
|
|
14
14
|
|
|
15
15
|
import { CellError, ErrorType, FunctionArgumentType } from '#hyperformula';
|
|
16
|
-
import { type AsyncFunction,
|
|
16
|
+
import { type AsyncFunction, AsyncFunctionPlugin } from './async-function';
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
export const EDGE_FUNCTION_NAME = 'DX';
|
|
19
|
+
|
|
20
|
+
const FUNCTION_TTL = 10_000;
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
|
-
* A hyperformula function plugin for calling EDGE functions.
|
|
23
|
+
* A hyperformula function plugin for calling remote (EDGE) functions.
|
|
22
24
|
*
|
|
23
25
|
* https://hyperformula.handsontable.com/guide/custom-functions.html#add-a-simple-custom-function
|
|
24
26
|
*/
|
|
25
|
-
export class EdgeFunctionPlugin extends
|
|
26
|
-
|
|
27
|
+
export class EdgeFunctionPlugin extends AsyncFunctionPlugin {
|
|
28
|
+
dx(ast: ProcedureAst, state: InterpreterState) {
|
|
27
29
|
const handler =
|
|
28
30
|
(subscribe = false): AsyncFunction =>
|
|
29
31
|
async (binding: string, ...args: any) => {
|
|
@@ -47,7 +49,7 @@ export class EdgeFunctionPlugin extends FunctionPluginAsync {
|
|
|
47
49
|
|
|
48
50
|
// TODO(wittjosiah): `ttl` should be 0 to force a recalculation when a new version is deployed.
|
|
49
51
|
// This needs a ttl to prevent a binding change from causing the function not to be found.
|
|
50
|
-
this.runAsyncFunction(ast, state, handler(false), { ttl:
|
|
52
|
+
this.runAsyncFunction(ast, state, handler(false), { ttl: FUNCTION_TTL });
|
|
51
53
|
});
|
|
52
54
|
|
|
53
55
|
this.context.createSubscription(ast.procedureName, unsubscribe);
|
|
@@ -63,19 +65,18 @@ export class EdgeFunctionPlugin extends FunctionPluginAsync {
|
|
|
63
65
|
return await result.text();
|
|
64
66
|
};
|
|
65
67
|
|
|
66
|
-
return this.runAsyncFunction(ast, state, handler(true), { ttl:
|
|
68
|
+
return this.runAsyncFunction(ast, state, handler(true), { ttl: FUNCTION_TTL });
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
EdgeFunctionPlugin.implementedFunctions = {
|
|
71
|
-
|
|
72
|
-
method: '
|
|
73
|
+
[EDGE_FUNCTION_NAME]: {
|
|
74
|
+
method: 'dx',
|
|
73
75
|
parameters: [
|
|
74
76
|
// Binding
|
|
75
77
|
{ argumentType: FunctionArgumentType.STRING },
|
|
76
78
|
|
|
77
|
-
// Remote function arguments (currently supporting up to
|
|
78
|
-
{ argumentType: FunctionArgumentType.ANY, optionalArg: true },
|
|
79
|
+
// Remote function arguments (currently supporting up to 8).
|
|
79
80
|
{ argumentType: FunctionArgumentType.ANY, optionalArg: true },
|
|
80
81
|
{ argumentType: FunctionArgumentType.ANY, optionalArg: true },
|
|
81
82
|
{ argumentType: FunctionArgumentType.ANY, optionalArg: true },
|
|
@@ -91,9 +92,9 @@ EdgeFunctionPlugin.implementedFunctions = {
|
|
|
91
92
|
|
|
92
93
|
export const EdgeFunctionPluginTranslations = {
|
|
93
94
|
enGB: {
|
|
94
|
-
|
|
95
|
+
[EDGE_FUNCTION_NAME]: 'Remote function',
|
|
95
96
|
},
|
|
96
97
|
enUS: {
|
|
97
|
-
|
|
98
|
+
[EDGE_FUNCTION_NAME]: 'Remote function',
|
|
98
99
|
},
|
|
99
100
|
};
|
|
@@ -7,8 +7,7 @@ import { describe, test, expect } from 'vitest';
|
|
|
7
7
|
import { HyperFormula } from '#hyperformula';
|
|
8
8
|
|
|
9
9
|
describe('hyperformula', () => {
|
|
10
|
-
test('sanity', async () => {
|
|
11
|
-
// TODO(burdon): Throws "Cannot convert undefined or null to object" in vitest (without browser).
|
|
10
|
+
test('sanity test', async () => {
|
|
12
11
|
const hf = HyperFormula.buildEmpty({ licenseKey: 'gpl-v3' });
|
|
13
12
|
expect(hf).to.exist;
|
|
14
13
|
});
|
package/src/graph/index.ts
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Client, type ClientOptions } from '@dxos/client';
|
|
6
|
+
import { type Context, Resource } from '@dxos/context';
|
|
7
|
+
import { invariant } from '@dxos/invariant';
|
|
8
|
+
|
|
9
|
+
import { type ComputeGraphOptions, ComputeGraphRegistry } from '../compute-graph-registry';
|
|
10
|
+
|
|
11
|
+
export type TestBuilderOptions = ClientOptions & ComputeGraphOptions;
|
|
12
|
+
|
|
13
|
+
// TODO(burdon): Reconcile with @dxos/client/testing.
|
|
14
|
+
export class TestBuilder extends Resource {
|
|
15
|
+
private _client?: Client;
|
|
16
|
+
private _registry?: ComputeGraphRegistry;
|
|
17
|
+
|
|
18
|
+
constructor(private readonly _options: TestBuilderOptions = {}) {
|
|
19
|
+
super();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get ctx(): Context {
|
|
23
|
+
return this._ctx;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get client(): Client {
|
|
27
|
+
invariant(this._client);
|
|
28
|
+
return this._client;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get registry(): ComputeGraphRegistry {
|
|
32
|
+
invariant(this._registry);
|
|
33
|
+
return this._registry;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override async _open() {
|
|
37
|
+
const client = new Client(this._options);
|
|
38
|
+
await client.initialize();
|
|
39
|
+
await client.halo.createIdentity();
|
|
40
|
+
this._client = client;
|
|
41
|
+
this._ctx.onDispose(async () => {
|
|
42
|
+
await client.destroy();
|
|
43
|
+
this._client = undefined;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const registry = new ComputeGraphRegistry(this._options);
|
|
47
|
+
await registry.open();
|
|
48
|
+
this._registry = registry;
|
|
49
|
+
this._ctx.onDispose(async () => {
|
|
50
|
+
await registry.close();
|
|
51
|
+
this._registry = undefined;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|