@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.
Files changed (158) hide show
  1. package/dist/lib/browser/{SheetContainer-V4GCCZTX.mjs → SheetContainer-LG77O4RM.mjs} +14 -13
  2. package/dist/lib/browser/SheetContainer-LG77O4RM.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-U2JHW3L6.mjs → chunk-CHQAW4F4.mjs} +206 -53
  4. package/dist/lib/browser/chunk-CHQAW4F4.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-6ZMQVB4Z.mjs → chunk-GSV5QNLD.mjs} +220 -177
  6. package/dist/lib/browser/chunk-GSV5QNLD.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-T3NJFTD4.mjs → chunk-WZMOZKQZ.mjs} +2 -2
  8. package/dist/lib/browser/{chunk-T3NJFTD4.mjs.map → chunk-WZMOZKQZ.mjs.map} +3 -3
  9. package/dist/lib/browser/graph-M4IQ76QX.mjs +33 -0
  10. package/dist/lib/browser/index.mjs +45 -21
  11. package/dist/lib/browser/index.mjs.map +4 -4
  12. package/dist/lib/browser/meta.json +1 -1
  13. package/dist/lib/browser/types.mjs +1 -1
  14. package/dist/lib/node/{SheetContainer-3ZY7MPWJ.cjs → SheetContainer-OZ7DHH4L.cjs} +21 -20
  15. package/dist/lib/node/SheetContainer-OZ7DHH4L.cjs.map +7 -0
  16. package/dist/lib/node/{chunk-OTTD7FBK.cjs → chunk-5FTFZL5W.cjs} +224 -70
  17. package/dist/lib/node/chunk-5FTFZL5W.cjs.map +7 -0
  18. package/dist/lib/node/{chunk-DD6FIXWC.cjs → chunk-5XPK2V4A.cjs} +222 -175
  19. package/dist/lib/node/chunk-5XPK2V4A.cjs.map +7 -0
  20. package/dist/lib/node/{chunk-Q3HBHPRL.cjs → chunk-AOP42UAA.cjs} +5 -5
  21. package/dist/lib/node/{chunk-Q3HBHPRL.cjs.map → chunk-AOP42UAA.cjs.map} +3 -3
  22. package/dist/lib/node/graph-Q3N2X26H.cjs +55 -0
  23. package/dist/lib/node/graph-Q3N2X26H.cjs.map +7 -0
  24. package/dist/lib/node/index.cjs +51 -30
  25. package/dist/lib/node/index.cjs.map +4 -4
  26. package/dist/lib/node/meta.json +1 -1
  27. package/dist/lib/node/types.cjs +8 -8
  28. package/dist/lib/node/types.cjs.map +1 -1
  29. package/dist/lib/node-esm/{SheetContainer-PXSJX6XK.mjs → SheetContainer-4XS2G25Z.mjs} +14 -13
  30. package/dist/lib/node-esm/SheetContainer-4XS2G25Z.mjs.map +7 -0
  31. package/dist/lib/node-esm/{chunk-D6KU5MI7.mjs → chunk-5WPZCXNS.mjs} +220 -177
  32. package/dist/lib/node-esm/chunk-5WPZCXNS.mjs.map +7 -0
  33. package/dist/lib/node-esm/{chunk-7HVSOTGA.mjs → chunk-KK3XL37M.mjs} +206 -53
  34. package/dist/lib/node-esm/chunk-KK3XL37M.mjs.map +7 -0
  35. package/dist/lib/node-esm/{chunk-BMNA27EX.mjs → chunk-RR2AO4SM.mjs} +2 -2
  36. package/dist/lib/node-esm/{chunk-BMNA27EX.mjs.map → chunk-RR2AO4SM.mjs.map} +3 -3
  37. package/dist/lib/node-esm/graph-SMPUMOV2.mjs +34 -0
  38. package/dist/lib/node-esm/index.mjs +45 -21
  39. package/dist/lib/node-esm/index.mjs.map +4 -4
  40. package/dist/lib/node-esm/meta.json +1 -1
  41. package/dist/lib/node-esm/types.mjs +1 -1
  42. package/dist/types/src/SheetPlugin.d.ts.map +1 -1
  43. package/dist/types/src/components/CellEditor/CellEditor.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/CellEditor/extension.d.ts.map +1 -1
  45. package/dist/types/src/components/GridSheet/GridSheet.d.ts +3 -3
  46. package/dist/types/src/components/GridSheet/GridSheet.d.ts.map +1 -1
  47. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts +1 -1
  48. package/dist/types/src/components/GridSheet/GridSheet.stories.d.ts.map +1 -1
  49. package/dist/types/src/components/Sheet/Sheet.d.ts.map +1 -1
  50. package/dist/types/src/components/Sheet/Sheet.stories.d.ts.map +1 -1
  51. package/dist/types/src/components/Sheet/sheet-context.d.ts +3 -3
  52. package/dist/types/src/components/Sheet/sheet-context.d.ts.map +1 -1
  53. package/dist/types/src/components/SheetContainer.d.ts +1 -1
  54. package/dist/types/src/components/SheetContainer.d.ts.map +1 -1
  55. package/dist/types/src/components/index.d.ts +1 -1
  56. package/dist/types/src/components/index.d.ts.map +1 -1
  57. package/dist/types/src/defs/types.d.ts.map +1 -1
  58. package/dist/types/src/defs/util.d.ts +1 -1
  59. package/dist/types/src/defs/util.d.ts.map +1 -1
  60. package/dist/types/src/extensions/compute.d.ts +3 -2
  61. package/dist/types/src/extensions/compute.d.ts.map +1 -1
  62. package/dist/types/src/extensions/compute.stories.d.ts.map +1 -1
  63. package/dist/types/src/graph/compute-graph-registry.d.ts +34 -0
  64. package/dist/types/src/graph/compute-graph-registry.d.ts.map +1 -0
  65. package/dist/types/src/graph/compute-graph.d.ts +17 -34
  66. package/dist/types/src/graph/compute-graph.d.ts.map +1 -1
  67. package/dist/types/src/graph/compute-graph.stories.d.ts.map +1 -1
  68. package/dist/types/src/graph/compute-graph.test.d.ts +2 -0
  69. package/dist/types/src/graph/compute-graph.test.d.ts.map +1 -0
  70. package/dist/types/src/graph/compute-node.d.ts +9 -2
  71. package/dist/types/src/graph/compute-node.d.ts.map +1 -1
  72. package/dist/types/src/graph/{async-function.d.ts → functions/async-function.d.ts} +13 -4
  73. package/dist/types/src/graph/functions/async-function.d.ts.map +1 -0
  74. package/dist/types/src/graph/functions/edge-function.d.ts +21 -0
  75. package/dist/types/src/graph/functions/edge-function.d.ts.map +1 -0
  76. package/dist/types/src/graph/functions/function-defs.d.ts.map +1 -0
  77. package/dist/types/src/graph/functions/index.d.ts +4 -0
  78. package/dist/types/src/graph/functions/index.d.ts.map +1 -0
  79. package/dist/types/src/graph/index.d.ts +2 -1
  80. package/dist/types/src/graph/index.d.ts.map +1 -1
  81. package/dist/types/src/graph/testing/index.d.ts +3 -0
  82. package/dist/types/src/graph/testing/index.d.ts.map +1 -0
  83. package/dist/types/src/graph/testing/test-builder.d.ts +15 -0
  84. package/dist/types/src/graph/testing/test-builder.d.ts.map +1 -0
  85. package/dist/types/src/graph/testing/test-plugin.d.ts +36 -0
  86. package/dist/types/src/graph/testing/test-plugin.d.ts.map +1 -0
  87. package/dist/types/src/hooks/useComputeGraph.d.ts.map +1 -1
  88. package/dist/types/src/hooks/useSheetModel.d.ts +2 -2
  89. package/dist/types/src/hooks/useSheetModel.d.ts.map +1 -1
  90. package/dist/types/src/model/sheet-model.d.ts +3 -3
  91. package/dist/types/src/model/sheet-model.d.ts.map +1 -1
  92. package/dist/types/src/model/sheet-model.test.d.ts +2 -0
  93. package/dist/types/src/model/sheet-model.test.d.ts.map +1 -0
  94. package/dist/types/src/testing/testing.d.ts +4 -5
  95. package/dist/types/src/testing/testing.d.ts.map +1 -1
  96. package/dist/types/src/types.d.ts +4 -3
  97. package/dist/types/src/types.d.ts.map +1 -1
  98. package/package.json +40 -39
  99. package/src/SheetPlugin.tsx +19 -15
  100. package/src/components/CellEditor/CellEditor.stories.tsx +2 -3
  101. package/src/components/CellEditor/extension.test.ts +0 -1
  102. package/src/components/CellEditor/extension.ts +4 -3
  103. package/src/components/GridSheet/GridSheet.stories.tsx +5 -4
  104. package/src/components/GridSheet/GridSheet.tsx +4 -4
  105. package/src/components/Sheet/Sheet.stories.tsx +21 -20
  106. package/src/components/Sheet/Sheet.tsx +30 -14
  107. package/src/components/Sheet/sheet-context.tsx +4 -4
  108. package/src/components/SheetContainer.tsx +13 -15
  109. package/src/defs/types.ts +1 -0
  110. package/src/defs/util.ts +19 -3
  111. package/src/extensions/compute.stories.tsx +20 -20
  112. package/src/extensions/compute.ts +91 -42
  113. package/src/graph/compute-graph-registry.ts +90 -0
  114. package/src/graph/compute-graph.stories.tsx +4 -3
  115. package/src/graph/compute-graph.test.ts +87 -0
  116. package/src/graph/compute-graph.ts +73 -121
  117. package/src/graph/compute-node.ts +17 -5
  118. package/src/graph/{async-function.ts → functions/async-function.ts} +23 -15
  119. package/src/graph/{edge-function.ts → functions/edge-function.ts} +14 -13
  120. package/src/graph/functions/index.ts +7 -0
  121. package/src/graph/hyperformula.test.ts +1 -2
  122. package/src/graph/index.ts +2 -1
  123. package/src/graph/testing/index.ts +6 -0
  124. package/src/graph/testing/test-builder.ts +54 -0
  125. package/src/graph/{custom-function.ts → testing/test-plugin.ts} +43 -9
  126. package/src/hooks/hooks.stories.tsx +3 -3
  127. package/src/hooks/useComputeGraph.ts +9 -1
  128. package/src/hooks/useSheetModel.ts +4 -7
  129. package/src/model/sheet-model.test.ts +59 -0
  130. package/src/model/sheet-model.ts +47 -30
  131. package/src/testing/testing.tsx +17 -15
  132. package/src/types.ts +3 -3
  133. package/dist/lib/browser/SheetContainer-V4GCCZTX.mjs.map +0 -7
  134. package/dist/lib/browser/chunk-6ZMQVB4Z.mjs.map +0 -7
  135. package/dist/lib/browser/chunk-U2JHW3L6.mjs.map +0 -7
  136. package/dist/lib/browser/graph-T27BOBOV.mjs +0 -21
  137. package/dist/lib/node/SheetContainer-3ZY7MPWJ.cjs.map +0 -7
  138. package/dist/lib/node/chunk-DD6FIXWC.cjs.map +0 -7
  139. package/dist/lib/node/chunk-OTTD7FBK.cjs.map +0 -7
  140. package/dist/lib/node/graph-SPKGX7W4.cjs +0 -43
  141. package/dist/lib/node/graph-SPKGX7W4.cjs.map +0 -7
  142. package/dist/lib/node-esm/SheetContainer-PXSJX6XK.mjs.map +0 -7
  143. package/dist/lib/node-esm/chunk-7HVSOTGA.mjs.map +0 -7
  144. package/dist/lib/node-esm/chunk-D6KU5MI7.mjs.map +0 -7
  145. package/dist/lib/node-esm/graph-U67IO4UC.mjs +0 -22
  146. package/dist/types/src/graph/async-function.d.ts.map +0 -1
  147. package/dist/types/src/graph/compute-graph.browser.test.d.ts +0 -2
  148. package/dist/types/src/graph/compute-graph.browser.test.d.ts.map +0 -1
  149. package/dist/types/src/graph/custom-function.d.ts +0 -21
  150. package/dist/types/src/graph/custom-function.d.ts.map +0 -1
  151. package/dist/types/src/graph/edge-function.d.ts +0 -20
  152. package/dist/types/src/graph/edge-function.d.ts.map +0 -1
  153. package/dist/types/src/graph/function-defs.d.ts.map +0 -1
  154. package/src/graph/compute-graph.browser.test.ts +0 -104
  155. /package/dist/lib/browser/{graph-T27BOBOV.mjs.map → graph-M4IQ76QX.mjs.map} +0 -0
  156. /package/dist/lib/node-esm/{graph-U67IO4UC.mjs.map → graph-SMPUMOV2.mjs.map} +0 -0
  157. /package/dist/types/src/graph/{function-defs.d.ts → functions/function-defs.d.ts} +0 -0
  158. /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 FunctionPluginDefinition } from 'hyperformula';
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 SpaceId, type Space, Filter, fullyQualifiedId } from '@dxos/client/echo';
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 { EdgeFunctionPlugin, EdgeFunctionPluginTranslations } from './edge-function';
22
- import { defaultFunctions, type FunctionDefinition } from './function-defs';
23
-
24
- //
25
- // NOTE: The package.json file defines the packaged #hyperformula module.
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) + 26 (object id) + 1 (separator).
28
+ const OBJECT_ID_LENGTH = 60; // 33 (space id) + 1 (separator) + 26 (object id).
30
29
 
31
- // TODO(burdon): Change to "DX".
32
- const CUSTOM_FUNCTION = 'ECHO';
30
+ // TODO(burdon): Factory.
31
+ // export type ComputeNodeGenerator = <T>(obj: T) => ComputeNode;
33
32
 
34
- export type ComputeGraphPlugin = {
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 PREFIX = '__';
58
- export const createSheetName = (id: string) => `${PREFIX}${id}`;
59
- export const getSheetId = (name: string): string | undefined =>
60
- name.startsWith(PREFIX) ? name.slice(PREFIX.length) : undefined;
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 _functions: FunctionType[] = [];
60
+ private _remoteFunctions: FunctionType[] = [];
127
61
 
128
- // The context is passed to all functions.
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
- // TODO(burdon): Typed events.
132
- // TODO(burdon): Tie into HyperFormula dependency graph.
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
- refresh() {
152
- log('refresh', { id: this.id });
153
- this.update.emit();
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._functions.map((fn) => ({ name: fn.binding! })) : []),
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
- // Sheet references.
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): What if not loaded?
145
+ // TODO(burdon): Cache map.
204
146
  const objects = this._hf
205
147
  .getSheetNames()
206
148
  .map((name) => {
207
- const id = getSheetId(name);
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 || obj.title === name) {
214
- return `${createSheetName(obj.id)}!`;
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
- // Functions.
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._functions.find((fn) => fn.binding === binding);
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 `${CUSTOM_FUNCTION}("${binding}")`;
176
+ return `${EDGE_FUNCTION_NAME}("${binding}")`;
231
177
  } else {
232
- return `${CUSTOM_FUNCTION}("${binding}", ${args})`;
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 === CUSTOM_FUNCTION || defaultFunctions.find((fn) => fn.name === 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._functions.find((fn) => fn.binding === binding);
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._functions.find((fn) => fullyQualifiedId(fn) === id);
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._functions = objects.filter(({ binding }) => binding);
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
- // TODO(burdon): Chaing events.
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
- get hf() {
34
- return this._graph.hf;
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
- recalculationDelay: number;
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
- recalculationDelay: 200,
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
- private readonly _onUpdate: () => void;
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(this);
83
- }, this._options.recalculationDelay);
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 FunctionPluginAsync extends FunctionPlugin {
163
+ export class AsyncFunctionPlugin extends FunctionPlugin {
159
164
  get context() {
160
165
  return this.config.context as FunctionContext;
161
166
  }
162
167
 
163
- runAsyncFunction(ast: ProcedureAst, state: InterpreterState, cb: AsyncFunction, options?: FunctionOptions) {
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, FunctionPluginAsync } from './async-function';
16
+ import { type AsyncFunction, AsyncFunctionPlugin } from './async-function';
17
17
 
18
- const EDGE_FUNCTION_TTL = 10_000;
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 FunctionPluginAsync {
26
- edge(ast: ProcedureAst, state: InterpreterState) {
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: EDGE_FUNCTION_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: EDGE_FUNCTION_TTL });
68
+ return this.runAsyncFunction(ast, state, handler(true), { ttl: FUNCTION_TTL });
67
69
  }
68
70
  }
69
71
 
70
72
  EdgeFunctionPlugin.implementedFunctions = {
71
- EDGE: {
72
- method: 'edge',
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 9).
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
- EDGE: 'EDGE',
95
+ [EDGE_FUNCTION_NAME]: 'Remote function',
95
96
  },
96
97
  enUS: {
97
- EDGE: 'EDGE',
98
+ [EDGE_FUNCTION_NAME]: 'Remote function',
98
99
  },
99
100
  };
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './async-function';
6
+ export * from './edge-function';
7
+ export * from './function-defs';
@@ -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
  });
@@ -3,5 +3,6 @@
3
3
  //
4
4
 
5
5
  export * from './compute-graph';
6
+ export * from './compute-graph-registry';
6
7
  export * from './compute-node';
7
- export * from './function-defs';
8
+ export * from './functions';
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './test-builder';
6
+ export * from './test-plugin';
@@ -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
+ }