@dxos/compute 0.8.4-main.ead640a → 0.8.4-main.f466a3d56e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +36 -1
  3. package/dist/lib/neutral/StorageService.mjs +22 -0
  4. package/dist/lib/neutral/chunk-AKVCVI6N.mjs +25 -0
  5. package/dist/lib/neutral/chunk-AKVCVI6N.mjs.map +7 -0
  6. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  7. package/dist/lib/neutral/chunk-LF2ANNYW.mjs +171 -0
  8. package/dist/lib/neutral/chunk-LF2ANNYW.mjs.map +7 -0
  9. package/dist/lib/neutral/chunk-LRCABAWU.mjs +17 -0
  10. package/dist/lib/neutral/chunk-LRCABAWU.mjs.map +7 -0
  11. package/dist/lib/neutral/chunk-RCLYF66G.mjs +21 -0
  12. package/dist/lib/neutral/chunk-RCLYF66G.mjs.map +7 -0
  13. package/dist/lib/neutral/chunk-SEBWOTOU.mjs +235 -0
  14. package/dist/lib/neutral/chunk-SEBWOTOU.mjs.map +7 -0
  15. package/dist/lib/neutral/chunk-VSICPGGY.mjs +53 -0
  16. package/dist/lib/neutral/chunk-VSICPGGY.mjs.map +7 -0
  17. package/dist/lib/neutral/chunk-XEVFBI6S.mjs +62 -0
  18. package/dist/lib/neutral/chunk-XEVFBI6S.mjs.map +7 -0
  19. package/dist/lib/neutral/fib-ZSJYREBW.mjs +10 -0
  20. package/dist/lib/neutral/fib-ZSJYREBW.mjs.map +7 -0
  21. package/dist/lib/neutral/index.mjs +1225 -0
  22. package/dist/lib/neutral/index.mjs.map +7 -0
  23. package/dist/lib/neutral/meta.json +1 -0
  24. package/dist/lib/neutral/reply-YH3BVOUT.mjs +10 -0
  25. package/dist/lib/neutral/reply-YH3BVOUT.mjs.map +7 -0
  26. package/dist/lib/neutral/sleep-TSPIQ5EY.mjs +10 -0
  27. package/dist/lib/neutral/sleep-TSPIQ5EY.mjs.map +7 -0
  28. package/dist/lib/neutral/testing/index.mjs +32 -0
  29. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  30. package/dist/types/src/Blueprint.d.ts +192 -0
  31. package/dist/types/src/Blueprint.d.ts.map +1 -0
  32. package/dist/types/src/Credential.d.ts +27 -0
  33. package/dist/types/src/Credential.d.ts.map +1 -0
  34. package/dist/types/src/LayerSpec.d.ts +56 -0
  35. package/dist/types/src/LayerSpec.d.ts.map +1 -0
  36. package/dist/types/src/McpServer.d.ts +27 -0
  37. package/dist/types/src/McpServer.d.ts.map +1 -0
  38. package/dist/types/src/Operation.d.ts +340 -0
  39. package/dist/types/src/Operation.d.ts.map +1 -0
  40. package/dist/types/src/OperationHandlerSet.d.ts +67 -0
  41. package/dist/types/src/OperationHandlerSet.d.ts.map +1 -0
  42. package/dist/types/src/OperationHandlerSet.test.d.ts +2 -0
  43. package/dist/types/src/OperationHandlerSet.test.d.ts.map +1 -0
  44. package/dist/types/src/OperationRegistry.d.ts +23 -0
  45. package/dist/types/src/OperationRegistry.d.ts.map +1 -0
  46. package/dist/types/src/Process.d.ts +247 -0
  47. package/dist/types/src/Process.d.ts.map +1 -0
  48. package/dist/types/src/Routine.d.ts +63 -0
  49. package/dist/types/src/Routine.d.ts.map +1 -0
  50. package/dist/types/src/Script.d.ts +22 -0
  51. package/dist/types/src/Script.d.ts.map +1 -0
  52. package/dist/types/src/ServiceResolver.d.ts +89 -0
  53. package/dist/types/src/ServiceResolver.d.ts.map +1 -0
  54. package/dist/types/src/StorageService.d.ts +58 -0
  55. package/dist/types/src/StorageService.d.ts.map +1 -0
  56. package/dist/types/src/Template.d.ts +62 -0
  57. package/dist/types/src/Template.d.ts.map +1 -0
  58. package/dist/types/src/Template.test.d.ts +2 -0
  59. package/dist/types/src/Template.test.d.ts.map +1 -0
  60. package/dist/types/src/Trace.d.ts +268 -0
  61. package/dist/types/src/Trace.d.ts.map +1 -0
  62. package/dist/types/src/Trigger.d.ts +150 -0
  63. package/dist/types/src/Trigger.d.ts.map +1 -0
  64. package/dist/types/src/TriggerEvent.d.ts +75 -0
  65. package/dist/types/src/TriggerEvent.d.ts.map +1 -0
  66. package/dist/types/src/errors.d.ts +308 -0
  67. package/dist/types/src/errors.d.ts.map +1 -0
  68. package/dist/types/src/index.d.ts +17 -5
  69. package/dist/types/src/index.d.ts.map +1 -1
  70. package/dist/types/src/testing/definitions.d.ts +11 -0
  71. package/dist/types/src/testing/definitions.d.ts.map +1 -0
  72. package/dist/types/src/testing/fib.d.ts +8 -0
  73. package/dist/types/src/testing/fib.d.ts.map +1 -0
  74. package/dist/types/src/testing/index.d.ts +6 -4
  75. package/dist/types/src/testing/index.d.ts.map +1 -1
  76. package/dist/types/src/testing/reply.d.ts +4 -0
  77. package/dist/types/src/testing/reply.d.ts.map +1 -0
  78. package/dist/types/src/testing/sleep.d.ts +6 -0
  79. package/dist/types/src/testing/sleep.d.ts.map +1 -0
  80. package/dist/types/src/umbrella.test.d.ts +2 -0
  81. package/dist/types/src/umbrella.test.d.ts.map +1 -0
  82. package/dist/types/tsconfig.tsbuildinfo +1 -1
  83. package/package.json +29 -40
  84. package/src/Blueprint.ts +303 -0
  85. package/src/Credential.ts +53 -0
  86. package/src/LayerSpec.ts +91 -0
  87. package/src/McpServer.ts +44 -0
  88. package/src/Operation.ts +593 -0
  89. package/src/OperationHandlerSet.test.ts +92 -0
  90. package/src/OperationHandlerSet.ts +174 -0
  91. package/src/OperationRegistry.ts +50 -0
  92. package/src/Process.ts +504 -0
  93. package/src/Routine.ts +75 -0
  94. package/src/Script.ts +40 -0
  95. package/src/ServiceResolver.ts +194 -0
  96. package/src/StorageService.ts +101 -0
  97. package/src/Template.test.ts +151 -0
  98. package/src/Template.ts +122 -0
  99. package/src/Trace.ts +368 -0
  100. package/src/Trigger.ts +209 -0
  101. package/src/TriggerEvent.ts +64 -0
  102. package/src/errors.ts +71 -0
  103. package/src/index.ts +18 -32
  104. package/src/testing/definitions.ts +49 -0
  105. package/src/testing/fib.ts +22 -0
  106. package/src/testing/index.ts +13 -5
  107. package/src/testing/reply.ts +18 -0
  108. package/src/testing/sleep.ts +16 -0
  109. package/src/umbrella.test.ts +51 -0
  110. package/dist/lib/browser/chunk-C7KLV2IU.mjs +0 -3144
  111. package/dist/lib/browser/chunk-C7KLV2IU.mjs.map +0 -7
  112. package/dist/lib/browser/index.mjs +0 -55
  113. package/dist/lib/browser/meta.json +0 -1
  114. package/dist/lib/browser/testing/index.mjs +0 -206
  115. package/dist/lib/browser/testing/index.mjs.map +0 -7
  116. package/dist/lib/node-esm/chunk-K37OSZ5O.mjs +0 -3146
  117. package/dist/lib/node-esm/chunk-K37OSZ5O.mjs.map +0 -7
  118. package/dist/lib/node-esm/index.mjs +0 -56
  119. package/dist/lib/node-esm/meta.json +0 -1
  120. package/dist/lib/node-esm/testing/index.mjs +0 -207
  121. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  122. package/dist/types/src/compute-graph-registry.d.ts +0 -37
  123. package/dist/types/src/compute-graph-registry.d.ts.map +0 -1
  124. package/dist/types/src/compute-graph-registry.test.d.ts +0 -2
  125. package/dist/types/src/compute-graph-registry.test.d.ts.map +0 -1
  126. package/dist/types/src/compute-graph.d.ts +0 -67
  127. package/dist/types/src/compute-graph.d.ts.map +0 -1
  128. package/dist/types/src/compute-graph.test.d.ts +0 -2
  129. package/dist/types/src/compute-graph.test.d.ts.map +0 -1
  130. package/dist/types/src/compute-node.d.ts +0 -24
  131. package/dist/types/src/compute-node.d.ts.map +0 -1
  132. package/dist/types/src/functions/async-function.d.ts +0 -68
  133. package/dist/types/src/functions/async-function.d.ts.map +0 -1
  134. package/dist/types/src/functions/edge-function.d.ts +0 -20
  135. package/dist/types/src/functions/edge-function.d.ts.map +0 -1
  136. package/dist/types/src/functions/function-defs.d.ts +0 -11
  137. package/dist/types/src/functions/function-defs.d.ts.map +0 -1
  138. package/dist/types/src/functions/index.d.ts +0 -4
  139. package/dist/types/src/functions/index.d.ts.map +0 -1
  140. package/dist/types/src/hyperformula.test.d.ts +0 -2
  141. package/dist/types/src/hyperformula.test.d.ts.map +0 -1
  142. package/dist/types/src/testing/test-builder.d.ts +0 -15
  143. package/dist/types/src/testing/test-builder.d.ts.map +0 -1
  144. package/dist/types/src/testing/test-plugin.d.ts +0 -35
  145. package/dist/types/src/testing/test-plugin.d.ts.map +0 -1
  146. package/dist/types/src/testing/test-runtime.d.ts +0 -10
  147. package/dist/types/src/testing/test-runtime.d.ts.map +0 -1
  148. package/dist/types/src/testing/util.d.ts +0 -2
  149. package/dist/types/src/testing/util.d.ts.map +0 -1
  150. package/dist/types/src/types/hyperformula.d.ts +0 -8
  151. package/dist/types/src/types/hyperformula.d.ts.map +0 -1
  152. package/dist/types/src/types/index.d.ts +0 -3
  153. package/dist/types/src/types/index.d.ts.map +0 -1
  154. package/dist/types/src/types/types.d.ts +0 -20
  155. package/dist/types/src/types/types.d.ts.map +0 -1
  156. package/dist/types/src/types/types.test.d.ts +0 -2
  157. package/dist/types/src/types/types.test.d.ts.map +0 -1
  158. package/dist/types/src/util.d.ts +0 -2
  159. package/dist/types/src/util.d.ts.map +0 -1
  160. package/src/compute-graph-registry.test.ts +0 -138
  161. package/src/compute-graph-registry.ts +0 -97
  162. package/src/compute-graph.test.ts +0 -88
  163. package/src/compute-graph.ts +0 -261
  164. package/src/compute-node.ts +0 -59
  165. package/src/functions/async-function.ts +0 -184
  166. package/src/functions/edge-function.ts +0 -114
  167. package/src/functions/function-defs.ts +0 -2427
  168. package/src/functions/index.ts +0 -7
  169. package/src/hyperformula.test.ts +0 -14
  170. package/src/testing/test-builder.ts +0 -60
  171. package/src/testing/test-plugin.ts +0 -98
  172. package/src/testing/test-runtime.ts +0 -38
  173. package/src/testing/util.ts +0 -9
  174. package/src/types/hyperformula.ts +0 -19
  175. package/src/types/index.ts +0 -6
  176. package/src/types/types.test.ts +0 -91
  177. package/src/types/types.ts +0 -80
  178. package/src/util.ts +0 -8
  179. /package/dist/lib/{browser/index.mjs.map → neutral/StorageService.mjs.map} +0 -0
  180. /package/dist/lib/{node-esm/index.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
@@ -1,20 +0,0 @@
1
- export type CellAddress = Record<'row' | 'col', number>;
2
- export type CellRange = {
3
- from: CellAddress;
4
- to?: CellAddress;
5
- };
6
- export type CompleteCellRange = {
7
- from: CellAddress;
8
- to: CellAddress;
9
- };
10
- export type CellScalarValue = number | string | boolean | null;
11
- export declare const RANGE_NOTATION: RegExp;
12
- export declare const isFormula: (value: any) => value is string;
13
- export declare const cellEquals: (a: CellAddress | undefined, b: CellAddress | undefined) => boolean;
14
- export declare const columnLetter: (col: number) => string;
15
- export declare const addressToA1Notation: ({ col, row }: CellAddress) => string;
16
- export declare const addressFromA1Notation: (ref: string) => CellAddress;
17
- export declare const rangeToA1Notation: (range: CellRange) => string;
18
- export declare const rangeFromA1Notation: (ref: string) => CellRange;
19
- export declare const inRange: (range: CellRange | undefined, cell: CellAddress) => boolean;
20
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/types/types.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,GAAG,KAAK,EAAE,MAAM,CAAC,CAAC;AAExD,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,EAAE,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAChE,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,WAAW,CAAA;CAAE,CAAC;AAEvE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAE/D,eAAO,MAAM,cAAc,QAAmC,CAAC;AAE/D,eAAO,MAAM,SAAS,GAAI,OAAO,GAAG,KAAG,KAAK,IAAI,MAA8D,CAAC;AAE/G,eAAO,MAAM,UAAU,GAAI,GAAG,WAAW,GAAG,SAAS,EAAE,GAAG,WAAW,GAAG,SAAS,YAEhF,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MAM1C,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,cAAc,WAAW,KAAG,MAE/D,CAAC;AAGF,eAAO,MAAM,qBAAqB,GAAI,KAAK,MAAM,KAAG,WAOnD,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,OAAO,SAAS,WAIjD,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,KAAK,MAAM,KAAG,SAGjD,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,OAAO,SAAS,GAAG,SAAS,EAAE,MAAM,WAAW,KAAG,OAwBzE,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.test.d.ts","sourceRoot":"","sources":["../../../../src/types/types.test.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export declare const parseNumberString: (str: string) => number;
2
- //# sourceMappingURL=util.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,MAE/C,CAAC"}
@@ -1,138 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import * as Schema from 'effect/Schema';
6
- import { describe, expect, onTestFinished, test } from 'vitest';
7
-
8
- import { Trigger } from '@dxos/async';
9
- import { fullyQualifiedId } from '@dxos/client/echo';
10
- import { FunctionDefinition, FunctionType } from '@dxos/functions';
11
-
12
- import { ComputeGraphRegistry, defaultPlugins } from './compute-graph-registry';
13
- import { TestBuilder, createMockedComputeRuntimeProvider } from './testing';
14
-
15
- describe('ComputeGraphRegistry', () => {
16
- const add = FunctionDefinition.make({
17
- key: 'add',
18
- name: 'add',
19
- description: 'Adds two numbers',
20
- inputSchema: Schema.Struct({ a: Schema.Number, b: Schema.Number }),
21
- outputSchema: Schema.Number,
22
- handler: ({ data }) => data.a + data.b,
23
- });
24
-
25
- test('invokes user function through compute graph', async () => {
26
- const computeRuntime = createMockedComputeRuntimeProvider({ functions: [add] });
27
- const testBuilder = new TestBuilder({ types: [FunctionType], computeRuntime });
28
- await testBuilder.open();
29
- onTestFinished(async () => {
30
- await testBuilder.close();
31
- });
32
-
33
- const space = await testBuilder.client.spaces.create();
34
-
35
- // Register Edge function plugin and runtime in the registry.
36
- const registry = new ComputeGraphRegistry({ plugins: defaultPlugins, computeRuntime });
37
- await registry.open();
38
- onTestFinished(async () => {
39
- await registry.close();
40
- });
41
-
42
- const graph = registry.createGraph(space);
43
- await graph.open();
44
- onTestFinished(async () => {
45
- await graph.close();
46
- });
47
-
48
- // Create a function object in ECHO with binding that maps to DX calls.
49
- const functionObj = FunctionDefinition.serialize(add);
50
- functionObj.binding = 'ADD';
51
- space.db.add(functionObj);
52
-
53
- const node = await graph.getOrCreateNode('sheet').open();
54
-
55
- // Wait for value update after async invocation completes.
56
- const trigger = new Trigger<number>();
57
- node.update.on(() => {
58
- const value = node.getValue({ row: 0, col: 0 });
59
- if (typeof value === 'number') {
60
- trigger.wake(value);
61
- }
62
- });
63
-
64
- // Set formula using binding; graph will map to DX("ADD", ...)
65
- node.setValue({ row: 0, col: 0 }, '=ADD(2, 3)');
66
-
67
- const result = await trigger.wait();
68
- expect(result).to.equal(5);
69
-
70
- // Sanity check: mapping to fully qualified id works as well.
71
- const bindingId = graph.mapFunctionBindingToId('ADD(2, 3)');
72
- expect(bindingId.startsWith(`${fullyQualifiedId(functionObj)}`)).to.be.true;
73
- });
74
-
75
- test('adding a function binding updates autocomplete and enables execution', async () => {
76
- const computeRuntime = createMockedComputeRuntimeProvider({ functions: [add] });
77
- const testBuilder = new TestBuilder({ types: [FunctionType], computeRuntime });
78
- await testBuilder.open();
79
- onTestFinished(async () => {
80
- await testBuilder.close();
81
- });
82
-
83
- const space = await testBuilder.client.spaces.create();
84
-
85
- const registry = new ComputeGraphRegistry({ plugins: defaultPlugins, computeRuntime });
86
- await registry.open();
87
- onTestFinished(async () => {
88
- await registry.close();
89
- });
90
-
91
- const graph = registry.createGraph(space);
92
- await graph.open();
93
- onTestFinished(async () => {
94
- await graph.close();
95
- });
96
-
97
- // Initially there should be no echo functions.
98
- expect(graph.getFunctions({ standard: false, echo: true }).length).to.equal(0);
99
-
100
- // Subscribe for function updates.
101
- const functionsUpdated = new Trigger<void>();
102
- const unsubscribe = graph.update.on((e) => {
103
- if (e.type === 'functionsUpdated') {
104
- functionsUpdated.wake();
105
- }
106
- });
107
- onTestFinished(unsubscribe);
108
-
109
- // Add a bound function to the space; graph should pick it up.
110
- const functionObj = FunctionDefinition.serialize(add);
111
- functionObj.binding = 'ADD';
112
- space.db.add(functionObj);
113
-
114
- await functionsUpdated.wait();
115
-
116
- // Autocomplete should now include the binding.
117
- const echoFunctions = graph.getFunctions({ standard: false, echo: true }).map((f) => f.name);
118
- expect(echoFunctions).toContain('ADD');
119
-
120
- // And the binding should execute when used in a formula.
121
- const node = await graph.getOrCreateNode('sheet').open();
122
- onTestFinished(async () => {
123
- await node.close();
124
- });
125
-
126
- const valueSettled = new Trigger<number>();
127
- node.update.on(() => {
128
- const value = node.getValue({ row: 0, col: 0 });
129
- if (typeof value === 'number') {
130
- valueSettled.wake(value);
131
- }
132
- });
133
-
134
- node.setValue({ row: 0, col: 0 }, '=ADD(2, 3)');
135
- const result = await valueSettled.wait();
136
- expect(result).to.equal(5);
137
- });
138
- });
@@ -1,97 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import type * as ManagedRuntime from 'effect/ManagedRuntime';
6
- import defaultsDeep from 'lodash.defaultsdeep';
7
-
8
- import { type Space, type SpaceId } from '@dxos/client/echo';
9
- import { Resource } from '@dxos/context';
10
- import { type FunctionInvocationService } from '@dxos/functions';
11
- import { invariant } from '@dxos/invariant';
12
- import { log } from '@dxos/log';
13
- import type { ConfigParams, FunctionPluginDefinition, FunctionTranslationsPackage } from '@dxos/vendor-hyperformula';
14
- import { HyperFormula } from '@dxos/vendor-hyperformula';
15
-
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 FunctionsRuntimeProvider = {
25
- getRuntime(spaceId: SpaceId): ManagedRuntime.ManagedRuntime<FunctionInvocationService, never>;
26
- };
27
-
28
- export type ComputeGraphOptions = {
29
- plugins?: ComputeGraphPlugin[];
30
- computeRuntime: FunctionsRuntimeProvider;
31
- } & Partial<FunctionContextOptions> &
32
- Partial<ConfigParams>;
33
-
34
- export const defaultPlugins: ComputeGraphPlugin[] = [
35
- {
36
- plugin: EdgeFunctionPlugin,
37
- translations: EdgeFunctionPluginTranslations,
38
- },
39
- ];
40
-
41
- export const defaultOptions: Partial<ComputeGraphOptions> = {
42
- licenseKey: 'gpl-v3',
43
- };
44
-
45
- /**
46
- * Manages a collection of ComputeGraph instances for each space.
47
- *
48
- * [ComputePlugin] => [ComputeGraphRegistry] => [ComputeGraph(Space)] => [ComputeNode(Object)]
49
- *
50
- * NOTE: The ComputeGraphRegistry manages the hierarchy of resources via its root Context.
51
- */
52
- // TODO(burdon): Move graph into separate plugin; isolate HF deps.
53
- export class ComputeGraphRegistry extends Resource {
54
- private readonly _graphs = new Map<SpaceId, ComputeGraph>();
55
- private readonly _options: ComputeGraphOptions;
56
-
57
- constructor(options: ComputeGraphOptions) {
58
- super();
59
- this._options = defaultsDeep({}, options, defaultOptions);
60
- this._options.plugins = this._options.plugins ?? defaultPlugins;
61
- this._options.plugins?.forEach(({ plugin, translations }) => {
62
- HyperFormula.registerFunctionPlugin(plugin, translations);
63
- });
64
- }
65
-
66
- getGraph(spaceId: SpaceId): ComputeGraph | undefined {
67
- return this._graphs.get(spaceId);
68
- }
69
-
70
- getOrCreateGraph(space: Space): ComputeGraph {
71
- let graph = this._graphs.get(space.id);
72
- if (!graph) {
73
- log('create graph', { space: space.id });
74
- graph = this.createGraph(space);
75
- }
76
-
77
- return graph;
78
- }
79
-
80
- createGraph(space: Space): ComputeGraph {
81
- invariant(!this._graphs.has(space.id), `ComputeGraph already exists for space: ${space.id}`);
82
- const hf = HyperFormula.buildEmpty(this._options);
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);
87
- this._graphs.set(space.id, graph);
88
- return graph;
89
- }
90
-
91
- protected override async _close(): Promise<void> {
92
- for (const graph of this._graphs.values()) {
93
- await graph.close();
94
- }
95
- this._graphs.clear();
96
- }
97
- }
@@ -1,88 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { afterEach, beforeEach, describe, expect, test } from 'vitest';
6
-
7
- import { Trigger } from '@dxos/async';
8
- import { fullyQualifiedId } from '@dxos/client/echo';
9
- import { Obj } from '@dxos/echo';
10
- import { FunctionType } from '@dxos/functions';
11
- import { type CellValue } from '@dxos/vendor-hyperformula';
12
- import { DetailedCellError } from '@dxos/vendor-hyperformula';
13
-
14
- import { TestBuilder } from './testing';
15
-
16
- describe('ComputeGraph', () => {
17
- let testBuilder: TestBuilder;
18
- beforeEach(async () => {
19
- testBuilder = new TestBuilder({ types: [FunctionType] });
20
- await testBuilder.open();
21
- });
22
- afterEach(async () => {
23
- await testBuilder.close();
24
- });
25
-
26
- test('map functions', async () => {
27
- const space = await testBuilder.client.spaces.create();
28
- const graph = testBuilder.registry.createGraph(space);
29
- await graph.open();
30
-
31
- // Create script.
32
- const trigger = new Trigger();
33
- graph.update.once(() => trigger.wake());
34
- const functionObject = space.db.add(Obj.make(FunctionType, { name: 'test', version: '0.0.1', binding: 'TEST' }));
35
- await trigger.wait();
36
- const functions = graph.getFunctions({ echo: true });
37
- expect(functions).to.toHaveLength(1);
38
-
39
- const id = graph.mapFunctionBindingToId('TEST()');
40
- expect(id).to.eq(`${fullyQualifiedId(functionObject)}()`);
41
- });
42
-
43
- test('cross-node references', async () => {
44
- const space = await testBuilder.client.spaces.create();
45
- const graph = testBuilder.registry.createGraph(space);
46
-
47
- // Create nodes.
48
- const node1 = await graph.getOrCreateNode('node-1').open();
49
- const node2 = await graph.getOrCreateNode('node-2').open();
50
- expect(graph.hf.getSheetNames()).to.toHaveLength(2);
51
-
52
- {
53
- node1.graph.hf.setCellContents({ sheet: node1.sheetId, row: 0, col: 0 }, [[100, 200, 300, '=SUM(A1:C1)']]);
54
- node2.graph.hf.setCellContents({ sheet: node2.sheetId, row: 0, col: 0 }, "='node-1'!D1");
55
- const value1 = node1.graph.hf.getCellValue({ sheet: node1.sheetId, col: 3, row: 0 });
56
- const value2 = node2.graph.hf.getCellValue({ sheet: node2.sheetId, col: 0, row: 0 });
57
- expect(value1).to.eq(value2);
58
- }
59
-
60
- // Get updated event.
61
- const trigger = new Trigger<CellValue>();
62
- node2.update.on(({ change }) => {
63
- const value = node2.graph.hf.getCellValue({ sheet: node2.sheetId, col: 0, row: 0 });
64
- expect(value).to.eq(change?.newValue);
65
- trigger.wake(value);
66
- });
67
-
68
- {
69
- node1.graph.hf.setCellContents({ sheet: node1.sheetId, row: 0, col: 0 }, 400);
70
- const value1 = node1.graph.hf.getCellValue({ sheet: node1.sheetId, col: 3, row: 0 });
71
- const value2 = await trigger.wait();
72
- expect(value1).to.eq(value2);
73
- }
74
- });
75
-
76
- // TODO(burdon): Dynamically load node/model based on dependencies.
77
- // - Create dependency then close model.
78
- test('dynamic loading', async () => {
79
- const space = await testBuilder.client.spaces.create();
80
- const graph = testBuilder.registry.createGraph(space);
81
-
82
- const node1 = await graph.getOrCreateNode('node-1').open();
83
- node1.graph.hf.setCellContents({ sheet: node1.sheetId, row: 0, col: 0 }, "='node-2'!A1");
84
- const value1 = node1.graph.hf.getCellValue({ sheet: node1.sheetId, col: 0, row: 0 });
85
- expect(value1).to.be.instanceof(DetailedCellError);
86
- expect((value1 as DetailedCellError).type).to.eq('REF');
87
- });
88
- });
@@ -1,261 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import type * as ManagedRuntime from 'effect/ManagedRuntime';
6
-
7
- import { Event } from '@dxos/async';
8
- import { Filter, type Space, fullyQualifiedId } from '@dxos/client/echo';
9
- import { FQ_ID_LENGTH } from '@dxos/client/echo';
10
- import { Resource } from '@dxos/context';
11
- import { getTypename } from '@dxos/echo/internal';
12
- import { type FunctionInvocationService, FunctionType } from '@dxos/functions';
13
- import { invariant } from '@dxos/invariant';
14
- import { PublicKey } from '@dxos/keys';
15
- import { log } from '@dxos/log';
16
- import { isNonNullable } from '@dxos/util';
17
- import type { Listeners } from '@dxos/vendor-hyperformula';
18
- import { ExportedCellChange, type HyperFormula } from '@dxos/vendor-hyperformula';
19
-
20
- import { ComputeNode } from './compute-node';
21
- import {
22
- EDGE_FUNCTION_NAME,
23
- FunctionContext,
24
- type FunctionContextOptions,
25
- type FunctionDefinition,
26
- defaultFunctions,
27
- } from './functions';
28
-
29
- // TODO(burdon): Factor out compute-graph.
30
-
31
- const UNKNOWN_BINDING = '__UNKNOWN__';
32
-
33
- // TODO(burdon): Factory.
34
- // export type ComputeNodeGenerator = <T>(obj: T) => ComputeNode;
35
-
36
- type ObjectRef = { type: string; id: string };
37
-
38
- /**
39
- * Marker for sheets that are managed by an ECHO object.
40
- * Sheet ID: `dxos.org/type/SheetType@1234`
41
- */
42
- export const createSheetName = ({ type, id }: ObjectRef) => `${type}@${id}`;
43
- export const parseSheetName = (name: string): Partial<ObjectRef> => {
44
- const [type, id] = name.split('@');
45
- return id ? { type, id } : { id: type };
46
- };
47
-
48
- export type ComputeGraphEvent = 'functionsUpdated' | 'valuesUpdated';
49
-
50
- /**
51
- * Per-space compute and dependency graph.
52
- * Consists of multiple ComputeNode (corresponding to a HyperFormula sheet).
53
- * Manages the set of custom functions.
54
- * HyperFormula manages the dependency graph.
55
- */
56
- export class ComputeGraph extends Resource {
57
- public readonly id = `graph-${PublicKey.random().truncate()}`;
58
-
59
- // Map of nodes indexed by sheet number.
60
- private readonly _nodes = new Map<number, ComputeNode>();
61
-
62
- // Cached function objects.
63
- private _remoteFunctions: FunctionType[] = [];
64
-
65
- public readonly update = new Event<{ type: ComputeGraphEvent }>();
66
-
67
- // The context is passed to all functions.
68
- public readonly context: FunctionContext;
69
-
70
- constructor(
71
- private readonly _hf: HyperFormula,
72
- private readonly _runtime: ManagedRuntime.ManagedRuntime<FunctionInvocationService, never>,
73
- private readonly _space?: Space,
74
- private readonly _options?: Partial<FunctionContextOptions>,
75
- ) {
76
- super();
77
-
78
- this.context = new FunctionContext(this._hf, this._runtime, this._space, {
79
- ...this._options,
80
- onUpdate: (update) => {
81
- this._options?.onUpdate?.(update);
82
- this.update.emit({ type: 'valuesUpdated' });
83
- },
84
- });
85
- this._hf.updateConfig({ context: this.context });
86
-
87
- // TODO(burdon): If debounce then aggregate changes.
88
- const onValuesUpdate: Listeners['valuesUpdated'] = (changes) => {
89
- for (const change of changes) {
90
- if (change instanceof ExportedCellChange) {
91
- const { sheet } = change;
92
- const node = this._nodes.get(sheet);
93
- if (node) {
94
- node.update.emit({ type: 'valuesUpdated', change });
95
- }
96
- }
97
- }
98
- };
99
-
100
- this._hf.on('valuesUpdated', onValuesUpdate);
101
- this._ctx.onDispose(() => this._hf.off('valuesUpdated', onValuesUpdate));
102
- }
103
-
104
- get hf() {
105
- return this._hf;
106
- }
107
-
108
- getFunctions(
109
- { standard, echo }: { standard?: boolean; echo?: boolean } = { standard: true, echo: true },
110
- ): FunctionDefinition[] {
111
- return [
112
- ...(standard
113
- ? this._hf
114
- .getRegisteredFunctionNames()
115
- .map((name) => defaultFunctions.find((fn) => fn.name === name) ?? { name })
116
- : []),
117
- ...(echo ? this._remoteFunctions.map((fn) => ({ name: fn.binding! })) : []),
118
- ];
119
- }
120
-
121
- /**
122
- * Get or create cell representing a sheet.
123
- */
124
- // TODO(burdon): Async (open node).
125
- // The graph should be an extensible factory that plugins extend with model constructors.
126
- // This would enable on-the-fly instantiation of new models when then are referenced.
127
- // E.g., Cross-object reference would be stored as "ObjectId!A1"
128
- // The graph would then load the object and create a ComputeNode (model) of the appropriate type.
129
- getOrCreateNode(name: string): ComputeNode {
130
- invariant(name.length);
131
- if (!this._hf.doesSheetExist(name)) {
132
- log('created node', { space: this._space?.id, sheet: name });
133
- this._hf.addSheet(name);
134
- }
135
-
136
- const sheetId = this._hf.getSheetId(name);
137
- invariant(sheetId !== undefined);
138
-
139
- const node = new ComputeNode(this, sheetId);
140
- this._nodes.set(sheetId, node);
141
- return node;
142
- }
143
-
144
- /**
145
- * Map bound value to custom function invocation.
146
- * E.g., "HELLO(...args)" => "DX("HELLO", ...args)".
147
- */
148
- mapFormulaToNative(formula: string): string {
149
- return (
150
- formula
151
- //
152
- // Map cross-sheet references by name onto sheet stored by ECHO object/model.
153
- // Example: "Test Sheet"!A0 => "dxos.org/type/SheetType@1234"!A0
154
- // https://hyperformula.handsontable.com/guide/cell-references.html#cell-references
155
- //
156
- .replace(/['"]?([ \w]+)['"]?!/, (_match, name) => {
157
- if (name) {
158
- // TODO(burdon): Cache map.
159
- const objects = this._hf
160
- .getSheetNames()
161
- .map((name) => {
162
- const { type, id } = parseSheetName(name);
163
- return type && id ? this._space?.db.getObjectById(id) : undefined;
164
- })
165
- .filter(isNonNullable);
166
-
167
- for (const obj of objects) {
168
- if (obj.name === name) {
169
- const type = getTypename(obj)!;
170
- // NOTE: Names must be single quoted.
171
- return `'${createSheetName({ type, id: obj.id })}'!`;
172
- }
173
- }
174
- }
175
-
176
- return `${name}!`;
177
- })
178
-
179
- //
180
- // Map remote function references (i.e., to remote DX function invocation).
181
- //
182
- .replace(/(\w+)\((.*)\)/g, (match, binding, args) => {
183
- const fn = this._remoteFunctions.find((fn) => fn.binding === binding);
184
- if (!fn) {
185
- return match;
186
- }
187
-
188
- if (args.trim() === '') {
189
- return `${EDGE_FUNCTION_NAME}("${binding}")`;
190
- } else {
191
- return `${EDGE_FUNCTION_NAME}("${binding}", ${args})`;
192
- }
193
- })
194
- );
195
- }
196
-
197
- /**
198
- * Map from binding to fully qualified ECHO ID (to store).
199
- * E.g., HELLO() => spaceId:objectId()
200
- */
201
- mapFunctionBindingToId(formula: string): string {
202
- return formula.replace(/(\w+)\((.*)\)/g, (match, binding, args) => {
203
- if (binding === EDGE_FUNCTION_NAME || defaultFunctions.find((fn) => fn.name === binding)) {
204
- return match;
205
- }
206
-
207
- const fn = this._remoteFunctions.find((fn) => fn.binding === binding);
208
- if (fn) {
209
- const id = fullyQualifiedId(fn);
210
- return `${id}(${args})`;
211
- } else {
212
- return match;
213
- }
214
- });
215
- }
216
-
217
- /**
218
- * Map from fully qualified ECHO ID to binding (from store).
219
- * E.g., spaceId:objectId() => HELLO()
220
- */
221
- mapFunctionBindingFromId(formula: string): string | undefined {
222
- const binding = formula.replace(/(\w+):([a-zA-Z0-9]+)\((.*)\)/g, (match, spaceId, objectId, args) => {
223
- const id = `${spaceId}:${objectId}`;
224
- if (id.length !== FQ_ID_LENGTH) {
225
- return match;
226
- }
227
-
228
- const fn = this._remoteFunctions.find((fn) => fullyQualifiedId(fn) === id);
229
- if (fn?.binding) {
230
- return `${fn.binding}(${args})`;
231
- } else {
232
- return UNKNOWN_BINDING;
233
- }
234
- });
235
-
236
- if (binding.startsWith(`=${UNKNOWN_BINDING}`)) {
237
- return undefined;
238
- } else {
239
- return binding;
240
- }
241
- }
242
-
243
- protected override async _open(): Promise<void> {
244
- if (this._space) {
245
- // Subscribe to remote function definitions.
246
- const query = this._space.db.query(Filter.type(FunctionType));
247
- const unsubscribe = query.subscribe(({ objects }) => {
248
- this._remoteFunctions = objects.filter(({ binding }) => binding);
249
- this.update.emit({ type: 'functionsUpdated' });
250
- });
251
-
252
- this._ctx.onDispose(unsubscribe);
253
- }
254
- }
255
-
256
- protected override async _close(): Promise<void> {
257
- for (const node of this._nodes.values()) {
258
- await node.close();
259
- }
260
- }
261
- }
@@ -1,59 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { Event } from '@dxos/async';
6
- import { Resource } from '@dxos/context';
7
- import { type ExportedCellChange, type Listeners } from '@dxos/vendor-hyperformula';
8
- import { DetailedCellError } from '@dxos/vendor-hyperformula';
9
-
10
- import { type ComputeGraph } from './compute-graph';
11
- import { type CellAddress, type CellScalarValue, isFormula } from './types';
12
-
13
- export type ComputeNodeEvent = {
14
- type: keyof Listeners;
15
- change?: ExportedCellChange;
16
- };
17
-
18
- /**
19
- * Individual "sheet" (typically corresponds to an ECHO object).
20
- */
21
- // TODO(burdon): Factor out common HF wrapper from from SheetModel.
22
- export class ComputeNode extends Resource {
23
- public readonly update = new Event<ComputeNodeEvent>();
24
-
25
- constructor(
26
- private readonly _graph: ComputeGraph,
27
- public readonly sheetId: number,
28
- ) {
29
- super();
30
- }
31
-
32
- get graph() {
33
- return this._graph;
34
- }
35
-
36
- clear(): void {
37
- this._graph.hf.clearSheet(this.sheetId);
38
- }
39
-
40
- getValue(cell: CellAddress): CellScalarValue {
41
- const value = this._graph.hf.getCellValue({ sheet: this.sheetId, row: cell.row, col: cell.col });
42
- if (value instanceof DetailedCellError) {
43
- return null;
44
- }
45
-
46
- return value;
47
- }
48
-
49
- setValue(cell: CellAddress, value: CellScalarValue): void {
50
- const mappedValue = isFormula(value) ? this._graph.mapFormulaToNative(value) : value;
51
- this._graph.hf.setCellContents({ sheet: this.sheetId, row: cell.row, col: cell.col }, [[mappedValue]]);
52
- }
53
-
54
- // TODO(burdon): Load data into sheet.
55
- protected override async _open(): Promise<void> {
56
- // const unsubscribe = this._graph.update.on(this.update.emit);
57
- // this._ctx.onDispose(unsubscribe);
58
- }
59
- }