@dxos/compute 0.8.4-main.f9ba587 → 0.8.4-main.fcfe5033a5
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/chunk-2PBHA7EI.mjs → neutral/chunk-JRV42MW3.mjs} +109 -86
- package/dist/lib/neutral/chunk-JRV42MW3.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/index.mjs +1 -1
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/{browser → neutral}/testing/index.mjs +52 -6
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/compute-graph-registry.d.ts +10 -7
- package/dist/types/src/compute-graph-registry.d.ts.map +1 -1
- package/dist/types/src/compute-graph-registry.test.d.ts +2 -0
- package/dist/types/src/compute-graph-registry.test.d.ts.map +1 -0
- package/dist/types/src/compute-graph.d.ts +10 -7
- package/dist/types/src/compute-graph.d.ts.map +1 -1
- package/dist/types/src/compute-node.d.ts +1 -2
- package/dist/types/src/compute-node.d.ts.map +1 -1
- package/dist/types/src/functions/async-function.d.ts +13 -11
- package/dist/types/src/functions/async-function.d.ts.map +1 -1
- package/dist/types/src/functions/edge-function.d.ts +3 -3
- package/dist/types/src/functions/edge-function.d.ts.map +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-builder.d.ts +1 -1
- package/dist/types/src/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/testing/test-plugin.d.ts +4 -5
- package/dist/types/src/testing/test-plugin.d.ts.map +1 -1
- package/dist/types/src/testing/test-runtime.d.ts +10 -0
- package/dist/types/src/testing/test-runtime.d.ts.map +1 -0
- package/dist/types/src/types/hyperformula.d.ts +3 -6
- package/dist/types/src/types/hyperformula.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +31 -33
- package/src/compute-graph-registry.test.ts +167 -0
- package/src/compute-graph-registry.ts +20 -13
- package/src/compute-graph.test.ts +63 -12
- package/src/compute-graph.ts +43 -37
- package/src/compute-node.ts +2 -4
- package/src/functions/async-function.ts +21 -16
- package/src/functions/edge-function.ts +28 -31
- package/src/hyperformula.test.ts +2 -2
- package/src/index.ts +0 -27
- package/src/testing/index.ts +1 -0
- package/src/testing/test-builder.ts +7 -2
- package/src/testing/test-plugin.ts +2 -4
- package/src/testing/test-runtime.ts +53 -0
- package/src/testing/util.ts +1 -1
- package/src/types/hyperformula.ts +8 -8
- package/src/types/types.test.ts +2 -2
- package/dist/lib/browser/chunk-2PBHA7EI.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-GTPQNWA3.mjs +0 -3080
- package/dist/lib/node-esm/chunk-GTPQNWA3.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -56
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -174
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/vendor/hyperformula.mjs +0 -36400
- /package/dist/lib/{browser → neutral}/index.mjs.map +0 -0
package/package.json
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/compute",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.fcfe5033a5",
|
|
4
4
|
"description": "Low-level compute API",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
7
11
|
"license": "MIT",
|
|
8
12
|
"author": "DXOS.org",
|
|
9
13
|
"sideEffects": true,
|
|
10
14
|
"type": "module",
|
|
11
|
-
"imports": {
|
|
12
|
-
"#hyperformula": {
|
|
13
|
-
"types": "hyperformula",
|
|
14
|
-
"default": "./dist/vendor/hyperformula.mjs"
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
15
|
"exports": {
|
|
18
16
|
".": {
|
|
17
|
+
"source": "./src/index.ts",
|
|
19
18
|
"types": "./dist/types/src/index.d.ts",
|
|
20
|
-
"
|
|
21
|
-
"node": "./dist/lib/node-esm/index.mjs"
|
|
19
|
+
"default": "./dist/lib/neutral/index.mjs"
|
|
22
20
|
},
|
|
23
21
|
"./testing": {
|
|
22
|
+
"source": "./src/testing/index.ts",
|
|
24
23
|
"types": "./dist/types/src/testing/index.d.ts",
|
|
25
|
-
"
|
|
26
|
-
"node": "./dist/lib/node-esm/testing/index.mjs"
|
|
24
|
+
"default": "./dist/lib/neutral/testing/index.mjs"
|
|
27
25
|
}
|
|
28
26
|
},
|
|
29
27
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -39,35 +37,35 @@
|
|
|
39
37
|
"src"
|
|
40
38
|
],
|
|
41
39
|
"dependencies": {
|
|
42
|
-
"
|
|
43
|
-
"effect": "3.16.13",
|
|
44
|
-
"hyperformula": "^2.7.1",
|
|
40
|
+
"effect": "3.20.0",
|
|
45
41
|
"lodash.defaultsdeep": "^4.6.1",
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/echo": "0.8.4-main.
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/functions": "0.8.4-main.
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/
|
|
58
|
-
"@dxos/protocols": "0.8.4-main.
|
|
59
|
-
"@dxos/schema": "0.8.4-main.
|
|
60
|
-
"@dxos/util": "0.8.4-main.
|
|
42
|
+
"@dxos/ai": "0.8.4-main.fcfe5033a5",
|
|
43
|
+
"@dxos/async": "0.8.4-main.fcfe5033a5",
|
|
44
|
+
"@dxos/debug": "0.8.4-main.fcfe5033a5",
|
|
45
|
+
"@dxos/client": "0.8.4-main.fcfe5033a5",
|
|
46
|
+
"@dxos/echo": "0.8.4-main.fcfe5033a5",
|
|
47
|
+
"@dxos/context": "0.8.4-main.fcfe5033a5",
|
|
48
|
+
"@dxos/functions-runtime": "0.8.4-main.fcfe5033a5",
|
|
49
|
+
"@dxos/invariant": "0.8.4-main.fcfe5033a5",
|
|
50
|
+
"@dxos/log": "0.8.4-main.fcfe5033a5",
|
|
51
|
+
"@dxos/operation": "0.8.4-main.fcfe5033a5",
|
|
52
|
+
"@dxos/plugin-automation": "0.8.4-main.fcfe5033a5",
|
|
53
|
+
"@dxos/keys": "0.8.4-main.fcfe5033a5",
|
|
54
|
+
"@dxos/protocols": "0.8.4-main.fcfe5033a5",
|
|
55
|
+
"@dxos/schema": "0.8.4-main.fcfe5033a5",
|
|
56
|
+
"@dxos/util": "0.8.4-main.fcfe5033a5",
|
|
57
|
+
"@dxos/functions": "0.8.4-main.fcfe5033a5",
|
|
58
|
+
"@dxos/vendor-hyperformula": "0.8.4-main.fcfe5033a5"
|
|
61
59
|
},
|
|
62
60
|
"devDependencies": {
|
|
63
61
|
"@tldraw/indices": "^2.0.0-alpha.14",
|
|
64
62
|
"@types/lodash.defaultsdeep": "^4.6.6",
|
|
65
|
-
"@dxos/echo
|
|
66
|
-
"@dxos/echo-
|
|
67
|
-
"@dxos/random": "0.8.4-main.
|
|
63
|
+
"@dxos/echo": "0.8.4-main.fcfe5033a5",
|
|
64
|
+
"@dxos/echo-db": "0.8.4-main.fcfe5033a5",
|
|
65
|
+
"@dxos/random": "0.8.4-main.fcfe5033a5"
|
|
68
66
|
},
|
|
69
67
|
"peerDependencies": {
|
|
70
|
-
"effect": "
|
|
68
|
+
"effect": "3.20.0"
|
|
71
69
|
},
|
|
72
70
|
"publishConfig": {
|
|
73
71
|
"access": "public"
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { Trigger } from '@dxos/async';
|
|
10
|
+
import { Obj } from '@dxos/echo';
|
|
11
|
+
import { Operation, OperationHandlerSet } from '@dxos/operation';
|
|
12
|
+
|
|
13
|
+
import { ComputeGraphRegistry, defaultPlugins } from './compute-graph-registry';
|
|
14
|
+
import { TestBuilder, createMockedComputeRuntimeProvider } from './testing';
|
|
15
|
+
|
|
16
|
+
const AddDefinition = Operation.make({
|
|
17
|
+
meta: {
|
|
18
|
+
key: 'add',
|
|
19
|
+
name: 'add',
|
|
20
|
+
description: 'Adds two numbers',
|
|
21
|
+
},
|
|
22
|
+
input: Schema.Struct({ a: Schema.Number, b: Schema.Number }),
|
|
23
|
+
output: Schema.Number,
|
|
24
|
+
services: [],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const add = AddDefinition.pipe(
|
|
28
|
+
Operation.withHandler(
|
|
29
|
+
Effect.fn(function* ({ a, b }) {
|
|
30
|
+
return a + b;
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
describe('ComputeGraphRegistry', () => {
|
|
36
|
+
test('invokes user function through compute graph', async () => {
|
|
37
|
+
const computeRuntime = createMockedComputeRuntimeProvider({
|
|
38
|
+
functions: OperationHandlerSet.make(add),
|
|
39
|
+
});
|
|
40
|
+
const testBuilder = new TestBuilder({
|
|
41
|
+
types: [Operation.PersistentOperation],
|
|
42
|
+
computeRuntime,
|
|
43
|
+
});
|
|
44
|
+
await testBuilder.open();
|
|
45
|
+
onTestFinished(async () => {
|
|
46
|
+
await testBuilder.close();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const space = await testBuilder.client.spaces.create();
|
|
50
|
+
|
|
51
|
+
// Register Edge function plugin and runtime in the registry.
|
|
52
|
+
const registry = new ComputeGraphRegistry({
|
|
53
|
+
plugins: defaultPlugins,
|
|
54
|
+
computeRuntime,
|
|
55
|
+
});
|
|
56
|
+
await registry.open();
|
|
57
|
+
onTestFinished(async () => {
|
|
58
|
+
await registry.close();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const graph = registry.createGraph(space);
|
|
62
|
+
await graph.open();
|
|
63
|
+
onTestFinished(async () => {
|
|
64
|
+
await graph.close();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const functionObj = Operation.serialize(add);
|
|
68
|
+
Obj.change(functionObj, (functionObj) => {
|
|
69
|
+
functionObj.binding = 'ADD';
|
|
70
|
+
});
|
|
71
|
+
space.db.add(functionObj);
|
|
72
|
+
|
|
73
|
+
const node = await graph.getOrCreateNode('sheet').open();
|
|
74
|
+
|
|
75
|
+
// Wait for value update after async invocation completes.
|
|
76
|
+
const trigger = new Trigger<number>();
|
|
77
|
+
node.update.on(() => {
|
|
78
|
+
const value = node.getValue({ row: 0, col: 0 });
|
|
79
|
+
if (typeof value === 'number') {
|
|
80
|
+
trigger.wake(value);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Set formula using binding; graph will map to DX("ADD", ...)
|
|
85
|
+
node.setValue({ row: 0, col: 0 }, '=ADD(2, 3)');
|
|
86
|
+
|
|
87
|
+
const result = await trigger.wait();
|
|
88
|
+
expect(result).to.equal(5);
|
|
89
|
+
|
|
90
|
+
// Sanity check: mapping to fully qualified id works as well.
|
|
91
|
+
const bindingId = graph.mapFunctionBindingToId('ADD(2, 3)');
|
|
92
|
+
expect(bindingId.startsWith(`${Obj.getDXN(functionObj).toString()}`)).to.be.true;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('adding a function binding updates autocomplete and enables execution', async () => {
|
|
96
|
+
const computeRuntime = createMockedComputeRuntimeProvider({
|
|
97
|
+
functions: OperationHandlerSet.make(add),
|
|
98
|
+
});
|
|
99
|
+
const testBuilder = new TestBuilder({
|
|
100
|
+
types: [Operation.PersistentOperation],
|
|
101
|
+
computeRuntime,
|
|
102
|
+
});
|
|
103
|
+
await testBuilder.open();
|
|
104
|
+
onTestFinished(async () => {
|
|
105
|
+
await testBuilder.close();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const space = await testBuilder.client.spaces.create();
|
|
109
|
+
|
|
110
|
+
const registry = new ComputeGraphRegistry({
|
|
111
|
+
plugins: defaultPlugins,
|
|
112
|
+
computeRuntime,
|
|
113
|
+
});
|
|
114
|
+
await registry.open();
|
|
115
|
+
onTestFinished(async () => {
|
|
116
|
+
await registry.close();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const graph = registry.createGraph(space);
|
|
120
|
+
await graph.open();
|
|
121
|
+
onTestFinished(async () => {
|
|
122
|
+
await graph.close();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Initially there should be no echo functions.
|
|
126
|
+
expect(graph.getFunctions({ standard: false, echo: true }).length).to.equal(0);
|
|
127
|
+
|
|
128
|
+
// Subscribe for function updates.
|
|
129
|
+
const functionsUpdated = new Trigger<void>();
|
|
130
|
+
const unsubscribe = graph.update.on((e) => {
|
|
131
|
+
if (e.type === 'functionsUpdated') {
|
|
132
|
+
functionsUpdated.wake();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
onTestFinished(unsubscribe);
|
|
136
|
+
|
|
137
|
+
const functionObj = Operation.serialize(add);
|
|
138
|
+
Obj.change(functionObj, (functionObj) => {
|
|
139
|
+
functionObj.binding = 'ADD';
|
|
140
|
+
});
|
|
141
|
+
space.db.add(functionObj);
|
|
142
|
+
|
|
143
|
+
await functionsUpdated.wait();
|
|
144
|
+
|
|
145
|
+
// Autocomplete should now include the binding.
|
|
146
|
+
const echoFunctions = graph.getFunctions({ standard: false, echo: true }).map((fn) => fn.name);
|
|
147
|
+
expect(echoFunctions).toContain('ADD');
|
|
148
|
+
|
|
149
|
+
// And the binding should execute when used in a formula.
|
|
150
|
+
const node = await graph.getOrCreateNode('sheet').open();
|
|
151
|
+
onTestFinished(async () => {
|
|
152
|
+
await node.close();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const valueSettled = new Trigger<number>();
|
|
156
|
+
node.update.on(() => {
|
|
157
|
+
const value = node.getValue({ row: 0, col: 0 });
|
|
158
|
+
if (typeof value === 'number') {
|
|
159
|
+
valueSettled.wake(value);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
node.setValue({ row: 0, col: 0 }, '=ADD(2, 3)');
|
|
164
|
+
const result = await valueSettled.wait();
|
|
165
|
+
expect(result).to.equal(5);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -2,17 +2,17 @@
|
|
|
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 * as ManagedRuntime from 'effect/ManagedRuntime';
|
|
8
6
|
import defaultsDeep from 'lodash.defaultsdeep';
|
|
9
7
|
|
|
10
|
-
import { type
|
|
8
|
+
import { type Space, type SpaceId } from '@dxos/client/echo';
|
|
11
9
|
import { Resource } from '@dxos/context';
|
|
12
10
|
import { invariant } from '@dxos/invariant';
|
|
13
11
|
import { log } from '@dxos/log';
|
|
12
|
+
import type { Operation } from '@dxos/operation';
|
|
13
|
+
import type { ConfigParams, FunctionPluginDefinition, FunctionTranslationsPackage } from '@dxos/vendor-hyperformula';
|
|
14
|
+
import { HyperFormula } from '@dxos/vendor-hyperformula';
|
|
14
15
|
|
|
15
|
-
import { HyperFormula } from '#hyperformula';
|
|
16
16
|
import { ComputeGraph } from './compute-graph';
|
|
17
17
|
import { EdgeFunctionPlugin, EdgeFunctionPluginTranslations, type FunctionContextOptions } from './functions';
|
|
18
18
|
|
|
@@ -21,15 +21,16 @@ export type ComputeGraphPlugin = {
|
|
|
21
21
|
translations: FunctionTranslationsPackage;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
export type FunctionsRuntimeProvider = {
|
|
25
|
+
getRuntime(spaceId: SpaceId): ManagedRuntime.ManagedRuntime<Operation.Service, never>;
|
|
26
|
+
};
|
|
27
|
+
|
|
24
28
|
export type ComputeGraphOptions = {
|
|
25
29
|
plugins?: ComputeGraphPlugin[];
|
|
30
|
+
computeRuntime: FunctionsRuntimeProvider;
|
|
26
31
|
} & Partial<FunctionContextOptions> &
|
|
27
32
|
Partial<ConfigParams>;
|
|
28
33
|
|
|
29
|
-
export const defaultOptions: ComputeGraphOptions = {
|
|
30
|
-
licenseKey: 'gpl-v3',
|
|
31
|
-
};
|
|
32
|
-
|
|
33
34
|
export const defaultPlugins: ComputeGraphPlugin[] = [
|
|
34
35
|
{
|
|
35
36
|
plugin: EdgeFunctionPlugin,
|
|
@@ -37,23 +38,26 @@ export const defaultPlugins: ComputeGraphPlugin[] = [
|
|
|
37
38
|
},
|
|
38
39
|
];
|
|
39
40
|
|
|
41
|
+
export const defaultOptions: Partial<ComputeGraphOptions> = {
|
|
42
|
+
licenseKey: 'gpl-v3',
|
|
43
|
+
};
|
|
44
|
+
|
|
40
45
|
/**
|
|
41
46
|
* Manages a collection of ComputeGraph instances for each space.
|
|
42
47
|
*
|
|
43
48
|
* [ComputePlugin] => [ComputeGraphRegistry] => [ComputeGraph(Space)] => [ComputeNode(Object)]
|
|
44
49
|
*
|
|
45
50
|
* NOTE: The ComputeGraphRegistry manages the hierarchy of resources via its root Context.
|
|
46
|
-
* NOTE: The package.json file defines the packaged #hyperformula module.
|
|
47
51
|
*/
|
|
48
52
|
// TODO(burdon): Move graph into separate plugin; isolate HF deps.
|
|
49
53
|
export class ComputeGraphRegistry extends Resource {
|
|
50
54
|
private readonly _graphs = new Map<SpaceId, ComputeGraph>();
|
|
51
|
-
|
|
52
55
|
private readonly _options: ComputeGraphOptions;
|
|
53
56
|
|
|
54
|
-
constructor(options: ComputeGraphOptions
|
|
57
|
+
constructor(options: ComputeGraphOptions) {
|
|
55
58
|
super();
|
|
56
59
|
this._options = defaultsDeep({}, options, defaultOptions);
|
|
60
|
+
this._options.plugins = this._options.plugins ?? defaultPlugins;
|
|
57
61
|
this._options.plugins?.forEach(({ plugin, translations }) => {
|
|
58
62
|
HyperFormula.registerFunctionPlugin(plugin, translations);
|
|
59
63
|
});
|
|
@@ -76,7 +80,10 @@ export class ComputeGraphRegistry extends Resource {
|
|
|
76
80
|
createGraph(space: Space): ComputeGraph {
|
|
77
81
|
invariant(!this._graphs.has(space.id), `ComputeGraph already exists for space: ${space.id}`);
|
|
78
82
|
const hf = HyperFormula.buildEmpty(this._options);
|
|
79
|
-
|
|
83
|
+
invariant(this._options.computeRuntime, 'ComputeRuntime is required');
|
|
84
|
+
const runtime = this._options.computeRuntime.getRuntime(space.id);
|
|
85
|
+
|
|
86
|
+
const graph = new ComputeGraph(hf, runtime, space, this._options);
|
|
80
87
|
this._graphs.set(space.id, graph);
|
|
81
88
|
return graph;
|
|
82
89
|
}
|
|
@@ -2,21 +2,20 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type CellValue } from 'hyperformula';
|
|
6
5
|
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
7
6
|
|
|
8
7
|
import { Trigger } from '@dxos/async';
|
|
9
|
-
import { fullyQualifiedId } from '@dxos/client/echo';
|
|
10
8
|
import { Obj } from '@dxos/echo';
|
|
11
|
-
import {
|
|
9
|
+
import { Operation } from '@dxos/operation';
|
|
10
|
+
import { type CellValue } from '@dxos/vendor-hyperformula';
|
|
11
|
+
import { DetailedCellError } from '@dxos/vendor-hyperformula';
|
|
12
12
|
|
|
13
|
-
import { DetailedCellError } from '#hyperformula';
|
|
14
13
|
import { TestBuilder } from './testing';
|
|
15
14
|
|
|
16
15
|
describe('ComputeGraph', () => {
|
|
17
16
|
let testBuilder: TestBuilder;
|
|
18
17
|
beforeEach(async () => {
|
|
19
|
-
testBuilder = new TestBuilder({ types: [
|
|
18
|
+
testBuilder = new TestBuilder({ types: [Operation.PersistentOperation] });
|
|
20
19
|
await testBuilder.open();
|
|
21
20
|
});
|
|
22
21
|
afterEach(async () => {
|
|
@@ -31,13 +30,45 @@ describe('ComputeGraph', () => {
|
|
|
31
30
|
// Create script.
|
|
32
31
|
const trigger = new Trigger();
|
|
33
32
|
graph.update.once(() => trigger.wake());
|
|
34
|
-
const functionObject = space.db.add(
|
|
33
|
+
const functionObject = space.db.add(
|
|
34
|
+
Obj.make(Operation.PersistentOperation, { name: 'test', version: '0.0.1', binding: 'TEST' }),
|
|
35
|
+
);
|
|
35
36
|
await trigger.wait();
|
|
36
37
|
const functions = graph.getFunctions({ echo: true });
|
|
37
38
|
expect(functions).to.toHaveLength(1);
|
|
38
39
|
|
|
39
40
|
const id = graph.mapFunctionBindingToId('TEST()');
|
|
40
|
-
expect(id).to.eq(`${
|
|
41
|
+
expect(id).to.eq(`${Obj.getDXN(functionObject).toString()}()`);
|
|
42
|
+
|
|
43
|
+
const restored = graph.mapFunctionBindingFromId(id);
|
|
44
|
+
expect(restored).to.eq('TEST()');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('map functions roundtrip with args', async () => {
|
|
48
|
+
const space = await testBuilder.client.spaces.create();
|
|
49
|
+
const graph = testBuilder.registry.createGraph(space);
|
|
50
|
+
await graph.open();
|
|
51
|
+
|
|
52
|
+
const trigger = new Trigger();
|
|
53
|
+
graph.update.once(() => trigger.wake());
|
|
54
|
+
space.db.add(Obj.make(Operation.PersistentOperation, { name: 'forex', version: '0.0.1', binding: 'FOREX' }));
|
|
55
|
+
await trigger.wait();
|
|
56
|
+
|
|
57
|
+
const stored = graph.mapFunctionBindingToId('=FOREX(C6,C7)');
|
|
58
|
+
expect(stored).to.include('dxn:echo:');
|
|
59
|
+
expect(stored).not.to.include('FOREX');
|
|
60
|
+
|
|
61
|
+
const restored = graph.mapFunctionBindingFromId(stored);
|
|
62
|
+
expect(restored).to.eq('=FOREX(C6,C7)');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('map functions unknown binding returns undefined', async () => {
|
|
66
|
+
const space = await testBuilder.client.spaces.create();
|
|
67
|
+
const graph = testBuilder.registry.createGraph(space);
|
|
68
|
+
await graph.open();
|
|
69
|
+
|
|
70
|
+
const result = graph.mapFunctionBindingFromId('=dxn:echo:@:01ABCDEF01ABCDEF01ABCDEF01(A1,B1)');
|
|
71
|
+
expect(result).to.be.undefined;
|
|
41
72
|
});
|
|
42
73
|
|
|
43
74
|
test('cross-node references', async () => {
|
|
@@ -52,22 +83,38 @@ describe('ComputeGraph', () => {
|
|
|
52
83
|
{
|
|
53
84
|
node1.graph.hf.setCellContents({ sheet: node1.sheetId, row: 0, col: 0 }, [[100, 200, 300, '=SUM(A1:C1)']]);
|
|
54
85
|
node2.graph.hf.setCellContents({ sheet: node2.sheetId, row: 0, col: 0 }, "='node-1'!D1");
|
|
55
|
-
const value1 = node1.graph.hf.getCellValue({
|
|
56
|
-
|
|
86
|
+
const value1 = node1.graph.hf.getCellValue({
|
|
87
|
+
sheet: node1.sheetId,
|
|
88
|
+
col: 3,
|
|
89
|
+
row: 0,
|
|
90
|
+
});
|
|
91
|
+
const value2 = node2.graph.hf.getCellValue({
|
|
92
|
+
sheet: node2.sheetId,
|
|
93
|
+
col: 0,
|
|
94
|
+
row: 0,
|
|
95
|
+
});
|
|
57
96
|
expect(value1).to.eq(value2);
|
|
58
97
|
}
|
|
59
98
|
|
|
60
99
|
// Get updated event.
|
|
61
100
|
const trigger = new Trigger<CellValue>();
|
|
62
101
|
node2.update.on(({ change }) => {
|
|
63
|
-
const value = node2.graph.hf.getCellValue({
|
|
102
|
+
const value = node2.graph.hf.getCellValue({
|
|
103
|
+
sheet: node2.sheetId,
|
|
104
|
+
col: 0,
|
|
105
|
+
row: 0,
|
|
106
|
+
});
|
|
64
107
|
expect(value).to.eq(change?.newValue);
|
|
65
108
|
trigger.wake(value);
|
|
66
109
|
});
|
|
67
110
|
|
|
68
111
|
{
|
|
69
112
|
node1.graph.hf.setCellContents({ sheet: node1.sheetId, row: 0, col: 0 }, 400);
|
|
70
|
-
const value1 = node1.graph.hf.getCellValue({
|
|
113
|
+
const value1 = node1.graph.hf.getCellValue({
|
|
114
|
+
sheet: node1.sheetId,
|
|
115
|
+
col: 3,
|
|
116
|
+
row: 0,
|
|
117
|
+
});
|
|
71
118
|
const value2 = await trigger.wait();
|
|
72
119
|
expect(value1).to.eq(value2);
|
|
73
120
|
}
|
|
@@ -81,7 +128,11 @@ describe('ComputeGraph', () => {
|
|
|
81
128
|
|
|
82
129
|
const node1 = await graph.getOrCreateNode('node-1').open();
|
|
83
130
|
node1.graph.hf.setCellContents({ sheet: node1.sheetId, row: 0, col: 0 }, "='node-2'!A1");
|
|
84
|
-
const value1 = node1.graph.hf.getCellValue({
|
|
131
|
+
const value1 = node1.graph.hf.getCellValue({
|
|
132
|
+
sheet: node1.sheetId,
|
|
133
|
+
col: 0,
|
|
134
|
+
row: 0,
|
|
135
|
+
});
|
|
85
136
|
expect(value1).to.be.instanceof(DetailedCellError);
|
|
86
137
|
expect((value1 as DetailedCellError).type).to.eq('REF');
|
|
87
138
|
});
|
package/src/compute-graph.ts
CHANGED
|
@@ -2,27 +2,27 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import type * as ManagedRuntime from 'effect/ManagedRuntime';
|
|
6
6
|
|
|
7
7
|
import { Event } from '@dxos/async';
|
|
8
|
-
import { type Space
|
|
9
|
-
import { FQ_ID_LENGTH } from '@dxos/client/echo';
|
|
8
|
+
import { Filter, type Space } from '@dxos/client/echo';
|
|
10
9
|
import { Resource } from '@dxos/context';
|
|
11
|
-
import {
|
|
12
|
-
import { FunctionType } from '@dxos/functions';
|
|
10
|
+
import { Obj } from '@dxos/echo';
|
|
13
11
|
import { invariant } from '@dxos/invariant';
|
|
14
12
|
import { PublicKey } from '@dxos/keys';
|
|
15
13
|
import { log } from '@dxos/log';
|
|
14
|
+
import { Operation } from '@dxos/operation';
|
|
16
15
|
import { isNonNullable } from '@dxos/util';
|
|
16
|
+
import type { Listeners } from '@dxos/vendor-hyperformula';
|
|
17
|
+
import { ExportedCellChange, type HyperFormula } from '@dxos/vendor-hyperformula';
|
|
17
18
|
|
|
18
|
-
import { ExportedCellChange, type HyperFormula } from '#hyperformula';
|
|
19
19
|
import { ComputeNode } from './compute-node';
|
|
20
20
|
import {
|
|
21
|
-
|
|
21
|
+
EDGE_FUNCTION_NAME,
|
|
22
22
|
FunctionContext,
|
|
23
23
|
type FunctionContextOptions,
|
|
24
24
|
type FunctionDefinition,
|
|
25
|
-
|
|
25
|
+
defaultFunctions,
|
|
26
26
|
} from './functions';
|
|
27
27
|
|
|
28
28
|
// TODO(burdon): Factor out compute-graph.
|
|
@@ -36,7 +36,7 @@ type ObjectRef = { type: string; id: string };
|
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Marker for sheets that are managed by an ECHO object.
|
|
39
|
-
* Sheet ID: `dxos.
|
|
39
|
+
* Sheet ID: `org.dxos.type.sheet-type@1234`
|
|
40
40
|
*/
|
|
41
41
|
export const createSheetName = ({ type, id }: ObjectRef) => `${type}@${id}`;
|
|
42
42
|
export const parseSheetName = (name: string): Partial<ObjectRef> => {
|
|
@@ -59,7 +59,7 @@ export class ComputeGraph extends Resource {
|
|
|
59
59
|
private readonly _nodes = new Map<number, ComputeNode>();
|
|
60
60
|
|
|
61
61
|
// Cached function objects.
|
|
62
|
-
private _remoteFunctions:
|
|
62
|
+
private _remoteFunctions: Operation.PersistentOperation[] = [];
|
|
63
63
|
|
|
64
64
|
public readonly update = new Event<{ type: ComputeGraphEvent }>();
|
|
65
65
|
|
|
@@ -68,12 +68,13 @@ export class ComputeGraph extends Resource {
|
|
|
68
68
|
|
|
69
69
|
constructor(
|
|
70
70
|
private readonly _hf: HyperFormula,
|
|
71
|
+
private readonly _runtime: ManagedRuntime.ManagedRuntime<Operation.Service, never>,
|
|
71
72
|
private readonly _space?: Space,
|
|
72
73
|
private readonly _options?: Partial<FunctionContextOptions>,
|
|
73
74
|
) {
|
|
74
75
|
super();
|
|
75
76
|
|
|
76
|
-
this.context = new FunctionContext(this._hf, this._space, {
|
|
77
|
+
this.context = new FunctionContext(this._hf, this._runtime, this._space, {
|
|
77
78
|
...this._options,
|
|
78
79
|
onUpdate: (update) => {
|
|
79
80
|
this._options?.onUpdate?.(update);
|
|
@@ -104,7 +105,10 @@ export class ComputeGraph extends Resource {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
getFunctions(
|
|
107
|
-
{ standard, echo }: { standard?: boolean; echo?: boolean } = {
|
|
108
|
+
{ standard, echo }: { standard?: boolean; echo?: boolean } = {
|
|
109
|
+
standard: true,
|
|
110
|
+
echo: true,
|
|
111
|
+
},
|
|
108
112
|
): FunctionDefinition[] {
|
|
109
113
|
return [
|
|
110
114
|
...(standard
|
|
@@ -148,7 +152,7 @@ export class ComputeGraph extends Resource {
|
|
|
148
152
|
formula
|
|
149
153
|
//
|
|
150
154
|
// Map cross-sheet references by name onto sheet stored by ECHO object/model.
|
|
151
|
-
// Example: "Test Sheet"!A0 => "dxos.
|
|
155
|
+
// Example: "Test Sheet"!A0 => "org.dxos.type.sheet-type@1234"!A0
|
|
152
156
|
// https://hyperformula.handsontable.com/guide/cell-references.html#cell-references
|
|
153
157
|
//
|
|
154
158
|
.replace(/['"]?([ \w]+)['"]?!/, (_match, name) => {
|
|
@@ -164,7 +168,7 @@ export class ComputeGraph extends Resource {
|
|
|
164
168
|
|
|
165
169
|
for (const obj of objects) {
|
|
166
170
|
if (obj.name === name) {
|
|
167
|
-
const type = getTypename(obj)!;
|
|
171
|
+
const type = Obj.getTypename(obj)!;
|
|
168
172
|
// NOTE: Names must be single quoted.
|
|
169
173
|
return `'${createSheetName({ type, id: obj.id })}'!`;
|
|
170
174
|
}
|
|
@@ -193,8 +197,8 @@ export class ComputeGraph extends Resource {
|
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
/**
|
|
196
|
-
* Map from binding to fully qualified ECHO
|
|
197
|
-
* E.g., HELLO() => spaceId:objectId()
|
|
200
|
+
* Map from binding to fully qualified ECHO DXN (to store).
|
|
201
|
+
* E.g., HELLO() => dxn:echo:spaceId:objectId()
|
|
198
202
|
*/
|
|
199
203
|
mapFunctionBindingToId(formula: string): string {
|
|
200
204
|
return formula.replace(/(\w+)\((.*)\)/g, (match, binding, args) => {
|
|
@@ -204,7 +208,7 @@ export class ComputeGraph extends Resource {
|
|
|
204
208
|
|
|
205
209
|
const fn = this._remoteFunctions.find((fn) => fn.binding === binding);
|
|
206
210
|
if (fn) {
|
|
207
|
-
const id =
|
|
211
|
+
const id = Obj.getDXN(fn).toString();
|
|
208
212
|
return `${id}(${args})`;
|
|
209
213
|
} else {
|
|
210
214
|
return match;
|
|
@@ -213,23 +217,22 @@ export class ComputeGraph extends Resource {
|
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
/**
|
|
216
|
-
* Map from fully qualified ECHO
|
|
217
|
-
* E.g., spaceId:objectId() => HELLO()
|
|
220
|
+
* Map from fully qualified ECHO DXN to binding (from store).
|
|
221
|
+
* E.g., dxn:echo:spaceId:objectId() => HELLO()
|
|
218
222
|
*/
|
|
219
223
|
mapFunctionBindingFromId(formula: string): string | undefined {
|
|
220
|
-
const binding = formula.replace(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
});
|
|
224
|
+
const binding = formula.replace(
|
|
225
|
+
/dxn:([^:]+):([^:(]+):([a-zA-Z0-9]+)\((.*)\)/g,
|
|
226
|
+
(_match, kind, spaceTag, objectId, args) => {
|
|
227
|
+
const dxn = `dxn:${kind}:${spaceTag}:${objectId}`;
|
|
228
|
+
const fn = this._remoteFunctions.find((fn) => Obj.getDXN(fn).toString() === dxn);
|
|
229
|
+
if (fn?.binding) {
|
|
230
|
+
return `${fn.binding}(${args})`;
|
|
231
|
+
} else {
|
|
232
|
+
return UNKNOWN_BINDING;
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
);
|
|
233
236
|
|
|
234
237
|
if (binding.startsWith(`=${UNKNOWN_BINDING}`)) {
|
|
235
238
|
return undefined;
|
|
@@ -241,11 +244,14 @@ export class ComputeGraph extends Resource {
|
|
|
241
244
|
protected override async _open(): Promise<void> {
|
|
242
245
|
if (this._space) {
|
|
243
246
|
// Subscribe to remote function definitions.
|
|
244
|
-
const query = this._space.db.query(Filter.type(
|
|
245
|
-
const unsubscribe = query.subscribe(
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
const query = this._space.db.query(Filter.type(Operation.PersistentOperation));
|
|
248
|
+
const unsubscribe = query.subscribe(
|
|
249
|
+
() => {
|
|
250
|
+
this._remoteFunctions = query.results.filter((fn) => fn.binding);
|
|
251
|
+
this.update.emit({ type: 'functionsUpdated' });
|
|
252
|
+
},
|
|
253
|
+
{ fire: true },
|
|
254
|
+
);
|
|
249
255
|
|
|
250
256
|
this._ctx.onDispose(unsubscribe);
|
|
251
257
|
}
|
package/src/compute-node.ts
CHANGED
|
@@ -2,13 +2,11 @@
|
|
|
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
|
-
|
|
8
5
|
import { Event } from '@dxos/async';
|
|
9
6
|
import { Resource } from '@dxos/context';
|
|
7
|
+
import { type ExportedCellChange, type Listeners } from '@dxos/vendor-hyperformula';
|
|
8
|
+
import { DetailedCellError } from '@dxos/vendor-hyperformula';
|
|
10
9
|
|
|
11
|
-
import { DetailedCellError } from '#hyperformula';
|
|
12
10
|
import { type ComputeGraph } from './compute-graph';
|
|
13
11
|
import { type CellAddress, type CellScalarValue, isFormula } from './types';
|
|
14
12
|
|