@dxos/compute 0.8.4-main.fcfe5033a5 → 0.8.4-staging.60fe92afc8

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 (176) 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/StorageService.mjs.map +7 -0
  5. package/dist/lib/neutral/chunk-2B2KSJT6.mjs +17 -0
  6. package/dist/lib/neutral/chunk-2B2KSJT6.mjs.map +7 -0
  7. package/dist/lib/neutral/chunk-AFBNVFAY.mjs +25 -0
  8. package/dist/lib/neutral/chunk-AFBNVFAY.mjs.map +7 -0
  9. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  10. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  11. package/dist/lib/neutral/chunk-JAU4E4EK.mjs +53 -0
  12. package/dist/lib/neutral/chunk-JAU4E4EK.mjs.map +7 -0
  13. package/dist/lib/neutral/chunk-MEPE4HHX.mjs +21 -0
  14. package/dist/lib/neutral/chunk-MEPE4HHX.mjs.map +7 -0
  15. package/dist/lib/neutral/chunk-QZS4G6PE.mjs +240 -0
  16. package/dist/lib/neutral/chunk-QZS4G6PE.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/chunk-Z3Y7FQGN.mjs +174 -0
  20. package/dist/lib/neutral/chunk-Z3Y7FQGN.mjs.map +7 -0
  21. package/dist/lib/neutral/fib-HBDUGQW7.mjs +10 -0
  22. package/dist/lib/neutral/fib-HBDUGQW7.mjs.map +7 -0
  23. package/dist/lib/neutral/index.mjs +1179 -51
  24. package/dist/lib/neutral/index.mjs.map +4 -4
  25. package/dist/lib/neutral/meta.json +1 -1
  26. package/dist/lib/neutral/reply-RJIAJ6FV.mjs +10 -0
  27. package/dist/lib/neutral/reply-RJIAJ6FV.mjs.map +7 -0
  28. package/dist/lib/neutral/sleep-3T2CZ7RE.mjs +10 -0
  29. package/dist/lib/neutral/sleep-3T2CZ7RE.mjs.map +7 -0
  30. package/dist/lib/neutral/testing/index.mjs +27 -214
  31. package/dist/lib/neutral/testing/index.mjs.map +4 -4
  32. package/dist/types/src/Blueprint.d.ts +182 -0
  33. package/dist/types/src/Blueprint.d.ts.map +1 -0
  34. package/dist/types/src/Credential.d.ts +27 -0
  35. package/dist/types/src/Credential.d.ts.map +1 -0
  36. package/dist/types/src/Header.d.ts +9 -0
  37. package/dist/types/src/Header.d.ts.map +1 -0
  38. package/dist/types/src/Header.test.d.ts +2 -0
  39. package/dist/types/src/Header.test.d.ts.map +1 -0
  40. package/dist/types/src/LayerSpec.d.ts +56 -0
  41. package/dist/types/src/LayerSpec.d.ts.map +1 -0
  42. package/dist/types/src/McpServer.d.ts +26 -0
  43. package/dist/types/src/McpServer.d.ts.map +1 -0
  44. package/dist/types/src/Operation.d.ts +385 -0
  45. package/dist/types/src/Operation.d.ts.map +1 -0
  46. package/dist/types/src/OperationHandlerSet.d.ts +69 -0
  47. package/dist/types/src/OperationHandlerSet.d.ts.map +1 -0
  48. package/dist/types/src/OperationHandlerSet.test.d.ts +2 -0
  49. package/dist/types/src/OperationHandlerSet.test.d.ts.map +1 -0
  50. package/dist/types/src/OperationRegistry.d.ts +23 -0
  51. package/dist/types/src/OperationRegistry.d.ts.map +1 -0
  52. package/dist/types/src/Process.d.ts +255 -0
  53. package/dist/types/src/Process.d.ts.map +1 -0
  54. package/dist/types/src/Routine.d.ts +54 -0
  55. package/dist/types/src/Routine.d.ts.map +1 -0
  56. package/dist/types/src/Script.d.ts +21 -0
  57. package/dist/types/src/Script.d.ts.map +1 -0
  58. package/dist/types/src/ServiceResolver.d.ts +92 -0
  59. package/dist/types/src/ServiceResolver.d.ts.map +1 -0
  60. package/dist/types/src/StorageService.d.ts +58 -0
  61. package/dist/types/src/StorageService.d.ts.map +1 -0
  62. package/dist/types/src/Template.d.ts +61 -0
  63. package/dist/types/src/Template.d.ts.map +1 -0
  64. package/dist/types/src/Template.test.d.ts +2 -0
  65. package/dist/types/src/Template.test.d.ts.map +1 -0
  66. package/dist/types/src/Trace.d.ts +266 -0
  67. package/dist/types/src/Trace.d.ts.map +1 -0
  68. package/dist/types/src/Trigger.d.ts +192 -0
  69. package/dist/types/src/Trigger.d.ts.map +1 -0
  70. package/dist/types/src/TriggerEvent.d.ts +83 -0
  71. package/dist/types/src/TriggerEvent.d.ts.map +1 -0
  72. package/dist/types/src/errors.d.ts +308 -0
  73. package/dist/types/src/errors.d.ts.map +1 -0
  74. package/dist/types/src/index.d.ts +18 -5
  75. package/dist/types/src/index.d.ts.map +1 -1
  76. package/dist/types/src/testing/definitions.d.ts +11 -0
  77. package/dist/types/src/testing/definitions.d.ts.map +1 -0
  78. package/dist/types/src/testing/fib.d.ts +8 -0
  79. package/dist/types/src/testing/fib.d.ts.map +1 -0
  80. package/dist/types/src/testing/index.d.ts +6 -4
  81. package/dist/types/src/testing/index.d.ts.map +1 -1
  82. package/dist/types/src/testing/reply.d.ts +4 -0
  83. package/dist/types/src/testing/reply.d.ts.map +1 -0
  84. package/dist/types/src/testing/sleep.d.ts +6 -0
  85. package/dist/types/src/testing/sleep.d.ts.map +1 -0
  86. package/dist/types/src/umbrella.test.d.ts +2 -0
  87. package/dist/types/src/umbrella.test.d.ts.map +1 -0
  88. package/dist/types/tsconfig.tsbuildinfo +1 -1
  89. package/package.json +25 -36
  90. package/src/Blueprint.ts +224 -0
  91. package/src/Credential.ts +53 -0
  92. package/src/Header.test.ts +132 -0
  93. package/src/Header.ts +38 -0
  94. package/src/LayerSpec.ts +91 -0
  95. package/src/McpServer.ts +44 -0
  96. package/src/Operation.ts +646 -0
  97. package/src/OperationHandlerSet.test.ts +97 -0
  98. package/src/OperationHandlerSet.ts +181 -0
  99. package/src/OperationRegistry.ts +50 -0
  100. package/src/Process.ts +541 -0
  101. package/src/Routine.ts +68 -0
  102. package/src/Script.ts +33 -0
  103. package/src/ServiceResolver.ts +200 -0
  104. package/src/StorageService.ts +101 -0
  105. package/src/Template.test.ts +164 -0
  106. package/src/Template.ts +125 -0
  107. package/src/Trace.ts +363 -0
  108. package/src/Trigger.ts +196 -0
  109. package/src/TriggerEvent.ts +64 -0
  110. package/src/errors.ts +71 -0
  111. package/src/index.ts +20 -6
  112. package/src/testing/definitions.ts +51 -0
  113. package/src/testing/fib.ts +22 -0
  114. package/src/testing/index.ts +13 -5
  115. package/src/testing/reply.ts +18 -0
  116. package/src/testing/sleep.ts +16 -0
  117. package/src/umbrella.test.ts +51 -0
  118. package/dist/lib/neutral/chunk-JRV42MW3.mjs +0 -3101
  119. package/dist/lib/neutral/chunk-JRV42MW3.mjs.map +0 -7
  120. package/dist/types/src/compute-graph-registry.d.ts +0 -37
  121. package/dist/types/src/compute-graph-registry.d.ts.map +0 -1
  122. package/dist/types/src/compute-graph-registry.test.d.ts +0 -2
  123. package/dist/types/src/compute-graph-registry.test.d.ts.map +0 -1
  124. package/dist/types/src/compute-graph.d.ts +0 -67
  125. package/dist/types/src/compute-graph.d.ts.map +0 -1
  126. package/dist/types/src/compute-graph.test.d.ts +0 -2
  127. package/dist/types/src/compute-graph.test.d.ts.map +0 -1
  128. package/dist/types/src/compute-node.d.ts +0 -24
  129. package/dist/types/src/compute-node.d.ts.map +0 -1
  130. package/dist/types/src/functions/async-function.d.ts +0 -68
  131. package/dist/types/src/functions/async-function.d.ts.map +0 -1
  132. package/dist/types/src/functions/edge-function.d.ts +0 -20
  133. package/dist/types/src/functions/edge-function.d.ts.map +0 -1
  134. package/dist/types/src/functions/function-defs.d.ts +0 -11
  135. package/dist/types/src/functions/function-defs.d.ts.map +0 -1
  136. package/dist/types/src/functions/index.d.ts +0 -4
  137. package/dist/types/src/functions/index.d.ts.map +0 -1
  138. package/dist/types/src/hyperformula.test.d.ts +0 -2
  139. package/dist/types/src/hyperformula.test.d.ts.map +0 -1
  140. package/dist/types/src/testing/test-builder.d.ts +0 -15
  141. package/dist/types/src/testing/test-builder.d.ts.map +0 -1
  142. package/dist/types/src/testing/test-plugin.d.ts +0 -35
  143. package/dist/types/src/testing/test-plugin.d.ts.map +0 -1
  144. package/dist/types/src/testing/test-runtime.d.ts +0 -10
  145. package/dist/types/src/testing/test-runtime.d.ts.map +0 -1
  146. package/dist/types/src/testing/util.d.ts +0 -2
  147. package/dist/types/src/testing/util.d.ts.map +0 -1
  148. package/dist/types/src/types/hyperformula.d.ts +0 -8
  149. package/dist/types/src/types/hyperformula.d.ts.map +0 -1
  150. package/dist/types/src/types/index.d.ts +0 -3
  151. package/dist/types/src/types/index.d.ts.map +0 -1
  152. package/dist/types/src/types/types.d.ts +0 -20
  153. package/dist/types/src/types/types.d.ts.map +0 -1
  154. package/dist/types/src/types/types.test.d.ts +0 -2
  155. package/dist/types/src/types/types.test.d.ts.map +0 -1
  156. package/dist/types/src/util.d.ts +0 -2
  157. package/dist/types/src/util.d.ts.map +0 -1
  158. package/src/compute-graph-registry.test.ts +0 -167
  159. package/src/compute-graph-registry.ts +0 -97
  160. package/src/compute-graph.test.ts +0 -139
  161. package/src/compute-graph.ts +0 -265
  162. package/src/compute-node.ts +0 -59
  163. package/src/functions/async-function.ts +0 -184
  164. package/src/functions/edge-function.ts +0 -108
  165. package/src/functions/function-defs.ts +0 -2427
  166. package/src/functions/index.ts +0 -7
  167. package/src/hyperformula.test.ts +0 -14
  168. package/src/testing/test-builder.ts +0 -59
  169. package/src/testing/test-plugin.ts +0 -98
  170. package/src/testing/test-runtime.ts +0 -53
  171. package/src/testing/util.ts +0 -9
  172. package/src/types/hyperformula.ts +0 -19
  173. package/src/types/index.ts +0 -6
  174. package/src/types/types.test.ts +0 -91
  175. package/src/types/types.ts +0 -80
  176. package/src/util.ts +0 -8
@@ -1,265 +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 } from '@dxos/client/echo';
9
- import { Resource } from '@dxos/context';
10
- import { Obj } from '@dxos/echo';
11
- import { invariant } from '@dxos/invariant';
12
- import { PublicKey } from '@dxos/keys';
13
- import { log } from '@dxos/log';
14
- import { Operation } from '@dxos/operation';
15
- import { isNonNullable } from '@dxos/util';
16
- import type { Listeners } from '@dxos/vendor-hyperformula';
17
- import { ExportedCellChange, type HyperFormula } from '@dxos/vendor-hyperformula';
18
-
19
- import { ComputeNode } from './compute-node';
20
- import {
21
- EDGE_FUNCTION_NAME,
22
- FunctionContext,
23
- type FunctionContextOptions,
24
- type FunctionDefinition,
25
- defaultFunctions,
26
- } from './functions';
27
-
28
- // TODO(burdon): Factor out compute-graph.
29
-
30
- const UNKNOWN_BINDING = '__UNKNOWN__';
31
-
32
- // TODO(burdon): Factory.
33
- // export type ComputeNodeGenerator = <T>(obj: T) => ComputeNode;
34
-
35
- type ObjectRef = { type: string; id: string };
36
-
37
- /**
38
- * Marker for sheets that are managed by an ECHO object.
39
- * Sheet ID: `org.dxos.type.sheet-type@1234`
40
- */
41
- export const createSheetName = ({ type, id }: ObjectRef) => `${type}@${id}`;
42
- export const parseSheetName = (name: string): Partial<ObjectRef> => {
43
- const [type, id] = name.split('@');
44
- return id ? { type, id } : { id: type };
45
- };
46
-
47
- export type ComputeGraphEvent = 'functionsUpdated' | 'valuesUpdated';
48
-
49
- /**
50
- * Per-space compute and dependency graph.
51
- * Consists of multiple ComputeNode (corresponding to a HyperFormula sheet).
52
- * Manages the set of custom functions.
53
- * HyperFormula manages the dependency graph.
54
- */
55
- export class ComputeGraph extends Resource {
56
- public readonly id = `graph-${PublicKey.random().truncate()}`;
57
-
58
- // Map of nodes indexed by sheet number.
59
- private readonly _nodes = new Map<number, ComputeNode>();
60
-
61
- // Cached function objects.
62
- private _remoteFunctions: Operation.PersistentOperation[] = [];
63
-
64
- public readonly update = new Event<{ type: ComputeGraphEvent }>();
65
-
66
- // The context is passed to all functions.
67
- public readonly context: FunctionContext;
68
-
69
- constructor(
70
- private readonly _hf: HyperFormula,
71
- private readonly _runtime: ManagedRuntime.ManagedRuntime<Operation.Service, never>,
72
- private readonly _space?: Space,
73
- private readonly _options?: Partial<FunctionContextOptions>,
74
- ) {
75
- super();
76
-
77
- this.context = new FunctionContext(this._hf, this._runtime, this._space, {
78
- ...this._options,
79
- onUpdate: (update) => {
80
- this._options?.onUpdate?.(update);
81
- this.update.emit({ type: 'valuesUpdated' });
82
- },
83
- });
84
- this._hf.updateConfig({ context: this.context });
85
-
86
- // TODO(burdon): If debounce then aggregate changes.
87
- const onValuesUpdate: Listeners['valuesUpdated'] = (changes) => {
88
- for (const change of changes) {
89
- if (change instanceof ExportedCellChange) {
90
- const { sheet } = change;
91
- const node = this._nodes.get(sheet);
92
- if (node) {
93
- node.update.emit({ type: 'valuesUpdated', change });
94
- }
95
- }
96
- }
97
- };
98
-
99
- this._hf.on('valuesUpdated', onValuesUpdate);
100
- this._ctx.onDispose(() => this._hf.off('valuesUpdated', onValuesUpdate));
101
- }
102
-
103
- get hf() {
104
- return this._hf;
105
- }
106
-
107
- getFunctions(
108
- { standard, echo }: { standard?: boolean; echo?: boolean } = {
109
- standard: true,
110
- echo: true,
111
- },
112
- ): FunctionDefinition[] {
113
- return [
114
- ...(standard
115
- ? this._hf
116
- .getRegisteredFunctionNames()
117
- .map((name) => defaultFunctions.find((fn) => fn.name === name) ?? { name })
118
- : []),
119
- ...(echo ? this._remoteFunctions.map((fn) => ({ name: fn.binding! })) : []),
120
- ];
121
- }
122
-
123
- /**
124
- * Get or create cell representing a sheet.
125
- */
126
- // TODO(burdon): Async (open node).
127
- // The graph should be an extensible factory that plugins extend with model constructors.
128
- // This would enable on-the-fly instantiation of new models when then are referenced.
129
- // E.g., Cross-object reference would be stored as "ObjectId!A1"
130
- // The graph would then load the object and create a ComputeNode (model) of the appropriate type.
131
- getOrCreateNode(name: string): ComputeNode {
132
- invariant(name.length);
133
- if (!this._hf.doesSheetExist(name)) {
134
- log('created node', { space: this._space?.id, sheet: name });
135
- this._hf.addSheet(name);
136
- }
137
-
138
- const sheetId = this._hf.getSheetId(name);
139
- invariant(sheetId !== undefined);
140
-
141
- const node = new ComputeNode(this, sheetId);
142
- this._nodes.set(sheetId, node);
143
- return node;
144
- }
145
-
146
- /**
147
- * Map bound value to custom function invocation.
148
- * E.g., "HELLO(...args)" => "DX("HELLO", ...args)".
149
- */
150
- mapFormulaToNative(formula: string): string {
151
- return (
152
- formula
153
- //
154
- // Map cross-sheet references by name onto sheet stored by ECHO object/model.
155
- // Example: "Test Sheet"!A0 => "org.dxos.type.sheet-type@1234"!A0
156
- // https://hyperformula.handsontable.com/guide/cell-references.html#cell-references
157
- //
158
- .replace(/['"]?([ \w]+)['"]?!/, (_match, name) => {
159
- if (name) {
160
- // TODO(burdon): Cache map.
161
- const objects = this._hf
162
- .getSheetNames()
163
- .map((name) => {
164
- const { type, id } = parseSheetName(name);
165
- return type && id ? this._space?.db.getObjectById(id) : undefined;
166
- })
167
- .filter(isNonNullable);
168
-
169
- for (const obj of objects) {
170
- if (obj.name === name) {
171
- const type = Obj.getTypename(obj)!;
172
- // NOTE: Names must be single quoted.
173
- return `'${createSheetName({ type, id: obj.id })}'!`;
174
- }
175
- }
176
- }
177
-
178
- return `${name}!`;
179
- })
180
-
181
- //
182
- // Map remote function references (i.e., to remote DX function invocation).
183
- //
184
- .replace(/(\w+)\((.*)\)/g, (match, binding, args) => {
185
- const fn = this._remoteFunctions.find((fn) => fn.binding === binding);
186
- if (!fn) {
187
- return match;
188
- }
189
-
190
- if (args.trim() === '') {
191
- return `${EDGE_FUNCTION_NAME}("${binding}")`;
192
- } else {
193
- return `${EDGE_FUNCTION_NAME}("${binding}", ${args})`;
194
- }
195
- })
196
- );
197
- }
198
-
199
- /**
200
- * Map from binding to fully qualified ECHO DXN (to store).
201
- * E.g., HELLO() => dxn:echo:spaceId:objectId()
202
- */
203
- mapFunctionBindingToId(formula: string): string {
204
- return formula.replace(/(\w+)\((.*)\)/g, (match, binding, args) => {
205
- if (binding === EDGE_FUNCTION_NAME || defaultFunctions.find((fn) => fn.name === binding)) {
206
- return match;
207
- }
208
-
209
- const fn = this._remoteFunctions.find((fn) => fn.binding === binding);
210
- if (fn) {
211
- const id = Obj.getDXN(fn).toString();
212
- return `${id}(${args})`;
213
- } else {
214
- return match;
215
- }
216
- });
217
- }
218
-
219
- /**
220
- * Map from fully qualified ECHO DXN to binding (from store).
221
- * E.g., dxn:echo:spaceId:objectId() => HELLO()
222
- */
223
- mapFunctionBindingFromId(formula: string): string | undefined {
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
- );
236
-
237
- if (binding.startsWith(`=${UNKNOWN_BINDING}`)) {
238
- return undefined;
239
- } else {
240
- return binding;
241
- }
242
- }
243
-
244
- protected override async _open(): Promise<void> {
245
- if (this._space) {
246
- // Subscribe to remote function definitions.
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
- );
255
-
256
- this._ctx.onDispose(unsubscribe);
257
- }
258
- }
259
-
260
- protected override async _close(): Promise<void> {
261
- for (const node of this._nodes.values()) {
262
- await node.close();
263
- }
264
- }
265
- }
@@ -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
- }
@@ -1,184 +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 CleanupFn, debounce } from '@dxos/async';
9
- import type { Space } from '@dxos/client/echo';
10
- import { log } from '@dxos/log';
11
- import type { Operation } from '@dxos/operation';
12
- import type { RawInterpreterValue, SimpleCellAddress } from '@dxos/vendor-hyperformula';
13
- import type { InterpreterState } from '@dxos/vendor-hyperformula';
14
- import type { InterpreterValue } from '@dxos/vendor-hyperformula';
15
- import type { ProcedureAst } from '@dxos/vendor-hyperformula';
16
- import { CellError, EmptyValue, ErrorType, FunctionPlugin, type HyperFormula } from '@dxos/vendor-hyperformula';
17
-
18
- // TODO(burdon): Create API gateways:
19
- // https://publicapis.io
20
- // https://api-ninjas.com/api/cryptoprice
21
- // https://developers.google.com/apis-explorer
22
- // https://publicapis.io/coin-desk-api
23
-
24
- export type AsyncFunction = (...args: any) => Promise<InterpreterValue>;
25
-
26
- export type FunctionUpdateEvent = {
27
- name: string;
28
- cell: SimpleCellAddress;
29
- };
30
-
31
- export type FunctionOptions = {
32
- ttl?: number;
33
- };
34
-
35
- export type FunctionContextOptions = {
36
- defaultTtl: number;
37
- debounceDelay: number;
38
- onUpdate?: (update: FunctionUpdateEvent) => void;
39
- };
40
-
41
- export const defaultFunctionContextOptions: Pick<FunctionContextOptions, 'defaultTtl' | 'debounceDelay'> = {
42
- defaultTtl: 5_000,
43
- debounceDelay: 200,
44
- };
45
-
46
- /**
47
- * The context singleton for the model is passed into custom functions.
48
- *
49
- * HyperFormula does not support async functions.
50
- * - https://hyperformula.handsontable.com/guide/custom-functions.html
51
- * - https://hyperformula.handsontable.com/guide/known-limitations.html#known-limitations
52
- * - https://github.com/handsontable/hyperformula/issues/892
53
- */
54
- export class FunctionContext {
55
- // Mangle name with params.
56
- static createInvocationKey(name: string, ...args: any): string {
57
- return JSON.stringify({ name, ...args });
58
- }
59
-
60
- // TODO(wittjosiah): Persist cached values.
61
- // Cached values for cell.
62
- private readonly _cache = new Map<string, { value: InterpreterValue; ts: number }>();
63
-
64
- // Active requests.
65
- private readonly _pending = new Map<string, number>();
66
-
67
- // Query subscriptions.
68
- private readonly _subscriptions = new Map<string, CleanupFn>();
69
-
70
- // Invocation count.
71
- private _invocations: Record<string, number> = {};
72
-
73
- private readonly _options: FunctionContextOptions;
74
-
75
- // Debounced update handler.
76
- private readonly _onUpdate: (update: FunctionUpdateEvent) => void;
77
-
78
- constructor(
79
- private readonly _hf: HyperFormula,
80
- private readonly _runtime: ManagedRuntime.ManagedRuntime<Operation.Service, never>,
81
- private readonly _space: Space | undefined,
82
- _options?: Partial<FunctionContextOptions>,
83
- ) {
84
- this._options = defaultsDeep(_options ?? {}, defaultFunctionContextOptions);
85
- this._onUpdate = debounce((update) => {
86
- log('update', update);
87
- // TODO(burdon): Better way to trigger recalculation? (NOTE: rebuildAndRecalculate resets the undo history.)
88
- this._hf.resumeEvaluation();
89
- this._options.onUpdate?.(update);
90
- }, this._options.debounceDelay);
91
- }
92
-
93
- get space() {
94
- return this._space;
95
- }
96
-
97
- get info() {
98
- return { cache: this._cache.size, invocations: this._invocations };
99
- }
100
-
101
- get runtime() {
102
- return this._runtime;
103
- }
104
-
105
- flush(): void {
106
- this._cache.clear();
107
- this._invocations = {};
108
- this._subscriptions.forEach((unsubscribe) => unsubscribe());
109
- this._subscriptions.clear();
110
- }
111
-
112
- createSubscription(name: string, unsubscribe: CleanupFn): void {
113
- this._subscriptions.get(name)?.();
114
- this._subscriptions.set(name, unsubscribe);
115
- }
116
-
117
- /**
118
- * Exec the function if TTL has expired.
119
- * Return the cached value.
120
- */
121
- invokeFunction(
122
- name: string,
123
- state: InterpreterState,
124
- args: any[],
125
- cb: AsyncFunction,
126
- options?: FunctionOptions,
127
- ): InterpreterValue | undefined {
128
- const ttl = options?.ttl ?? this._options.defaultTtl;
129
-
130
- const { formulaAddress: cell } = state;
131
- const invocationKey = FunctionContext.createInvocationKey(name, ...args);
132
- const { value = undefined, ts = 0 } = this._cache.get(invocationKey) ?? {};
133
-
134
- const now = Date.now();
135
- const delta = now - ts;
136
- if ((!ts || delta > ttl) && !this._pending.has(invocationKey)) {
137
- this._pending.set(invocationKey, now);
138
- setTimeout(async () => {
139
- this._invocations[name] = (this._invocations[name] ?? 0) + 1;
140
- try {
141
- // Exec function.
142
- const value = await cb(...args);
143
- this._cache.set(invocationKey, { value, ts: Date.now() });
144
- log('set', { cell, value });
145
- this._onUpdate({ name, cell });
146
- } catch (err) {
147
- // TODO(burdon): Show error to user.
148
- log.warn('failed', { cell, err });
149
- this._cache.set(invocationKey, { value: new CellError(ErrorType.ERROR, 'Function failed.'), ts: Date.now() });
150
- } finally {
151
- this._pending.delete(invocationKey);
152
- }
153
- });
154
- }
155
-
156
- log.info('invoke', { cell, name, args, cache: value });
157
- return value;
158
- }
159
- }
160
-
161
- /**
162
- * Base class for async functions.
163
- */
164
- export class AsyncFunctionPlugin extends FunctionPlugin {
165
- get context() {
166
- return this.config.context as FunctionContext;
167
- }
168
-
169
- /**
170
- * Immediately returns cached value then runs the async function.
171
- */
172
- protected runAsyncFunction(
173
- ast: ProcedureAst,
174
- state: InterpreterState,
175
- cb: AsyncFunction,
176
- options?: FunctionOptions,
177
- ): RawInterpreterValue {
178
- const { procedureName } = ast;
179
- const metadata = this.metadata(procedureName);
180
- return this.runFunction(ast.args, state, metadata, (...args: any) => {
181
- return this.context.invokeFunction(procedureName, state, args, cb, options) ?? EmptyValue;
182
- });
183
- }
184
- }
@@ -1,108 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import * as Effect from 'effect/Effect';
6
- import * as SchemaAST from 'effect/SchemaAST';
7
-
8
- import { Filter } from '@dxos/client/echo';
9
- import { JsonSchema, Obj } from '@dxos/echo';
10
- import { log } from '@dxos/log';
11
- import { Operation } from '@dxos/operation';
12
- import { isNonNullable } from '@dxos/util';
13
- import { type ProcedureAst } from '@dxos/vendor-hyperformula';
14
- import { type InterpreterState } from '@dxos/vendor-hyperformula';
15
- import { CellError, ErrorType, FunctionArgumentType } from '@dxos/vendor-hyperformula';
16
-
17
- import { type AsyncFunction, AsyncFunctionPlugin } from './async-function';
18
-
19
- export const EDGE_FUNCTION_NAME = 'DX';
20
-
21
- const FUNCTION_TTL = 10_000;
22
-
23
- /**
24
- * A hyperformula function plugin for calling remote (EDGE) functions.
25
- * https://hyperformula.handsontable.com/guide/custom-functions.html#add-a-simple-custom-function
26
- */
27
- export class EdgeFunctionPlugin extends AsyncFunctionPlugin {
28
- dx(ast: ProcedureAst, state: InterpreterState) {
29
- const handler =
30
- (subscribe = false): AsyncFunction =>
31
- async (binding: string, ...args: any) => {
32
- const space = this.context.space;
33
- if (!space) {
34
- return new CellError(ErrorType.REF, 'Missing space');
35
- }
36
-
37
- const [fn] = await space.db.query(Filter.type(Operation.PersistentOperation, { binding })).run();
38
- if (!fn) {
39
- log.info('Function not found', { binding });
40
- return new CellError(ErrorType.REF, 'Function not found');
41
- }
42
-
43
- if (subscribe) {
44
- const unsubscribe = Obj.subscribe(fn, () => {
45
- log('function changed', { fn });
46
- // TODO(wittjosiah): `ttl` should be 0 to force a recalculation when a new version is deployed.
47
- // This needs a ttl to prevent a binding change from causing the function not to be found.
48
- this.runAsyncFunction(ast, state, handler(false), {
49
- ttl: FUNCTION_TTL,
50
- });
51
- });
52
-
53
- this.context.createSubscription(ast.procedureName, unsubscribe);
54
- }
55
-
56
- const runtime = this.context.runtime;
57
- const functionDef = Operation.deserialize(fn);
58
- // If input schema exists, construct an object from args using the schema props order, otherwise pass { args }.
59
- let input: any;
60
- if (fn.inputSchema) {
61
- const schema = JsonSchema.toEffectSchema(fn.inputSchema);
62
- const props = SchemaAST.getPropertySignatures(schema.ast);
63
- input = {} as any;
64
- props.forEach(({ name }, index) => {
65
- input[name.toString()] = args[index];
66
- });
67
- } else {
68
- input = { args: args.filter(isNonNullable) };
69
- }
70
- const result = runtime.runPromise(Operation.invoke(functionDef, input).pipe(Effect.orDie));
71
- return result as any;
72
- };
73
-
74
- return this.runAsyncFunction(ast, state, handler(true), {
75
- ttl: FUNCTION_TTL,
76
- });
77
- }
78
- }
79
-
80
- EdgeFunctionPlugin.implementedFunctions = {
81
- [EDGE_FUNCTION_NAME]: {
82
- method: 'dx',
83
- parameters: [
84
- // Binding
85
- { argumentType: FunctionArgumentType.STRING },
86
-
87
- // Remote function arguments (currently supporting up to 8).
88
- { argumentType: FunctionArgumentType.ANY, optionalArg: true },
89
- { argumentType: FunctionArgumentType.ANY, optionalArg: true },
90
- { argumentType: FunctionArgumentType.ANY, optionalArg: true },
91
- { argumentType: FunctionArgumentType.ANY, optionalArg: true },
92
- { argumentType: FunctionArgumentType.ANY, optionalArg: true },
93
- { argumentType: FunctionArgumentType.ANY, optionalArg: true },
94
- { argumentType: FunctionArgumentType.ANY, optionalArg: true },
95
- { argumentType: FunctionArgumentType.ANY, optionalArg: true },
96
- ],
97
- isVolatile: true,
98
- },
99
- };
100
-
101
- export const EdgeFunctionPluginTranslations = {
102
- enGB: {
103
- [EDGE_FUNCTION_NAME]: 'Remote function',
104
- },
105
- enUS: {
106
- [EDGE_FUNCTION_NAME]: 'Remote function',
107
- },
108
- };