@dxos/plugin-automation 0.8.4-main.5ad4a44 → 0.8.4-main.66e292d

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 (164) hide show
  1. package/dist/lib/browser/AutomationPanel-M4M77L4V.mjs +11 -0
  2. package/dist/lib/browser/{AutomationSettings-3LABN6ER.mjs → AutomationSettings-4HCI6KJR.mjs} +6 -7
  3. package/dist/lib/{node-esm/AutomationSettings-XENNIIZM.mjs.map → browser/AutomationSettings-4HCI6KJR.mjs.map} +1 -1
  4. package/dist/lib/browser/FunctionsContainer-CDVBRQCT.mjs +144 -0
  5. package/dist/lib/browser/FunctionsContainer-CDVBRQCT.mjs.map +7 -0
  6. package/dist/lib/browser/{FunctionsPanel-NRIKAPQV.mjs → FunctionsPanel-CRW6SJUN.mjs} +3 -3
  7. package/dist/lib/browser/{app-graph-builder-DV5HMFX4.mjs → app-graph-builder-W7LLC6XW.mjs} +7 -7
  8. package/dist/lib/browser/app-graph-builder-W7LLC6XW.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-4MBM6C6A.mjs +100 -0
  10. package/dist/lib/browser/chunk-4MBM6C6A.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-NIJWEQRD.mjs → chunk-7W6QMY3L.mjs} +26 -42
  12. package/dist/lib/browser/chunk-7W6QMY3L.mjs.map +7 -0
  13. package/dist/lib/browser/{chunk-AY67OUDA.mjs → chunk-CZVA5NMD.mjs} +5 -5
  14. package/dist/lib/browser/chunk-LZQFZO3B.mjs +17 -0
  15. package/dist/lib/browser/chunk-LZQFZO3B.mjs.map +7 -0
  16. package/dist/lib/browser/{chunk-LC3QQU47.mjs → chunk-T6ZESHDY.mjs} +38 -18
  17. package/dist/lib/browser/chunk-T6ZESHDY.mjs.map +7 -0
  18. package/dist/lib/browser/{chunk-DLLE4FKP.mjs → chunk-TWWFNOIR.mjs} +27 -12
  19. package/dist/lib/browser/chunk-TWWFNOIR.mjs.map +7 -0
  20. package/dist/lib/browser/{chunk-5ARH77PV.mjs → chunk-XAKZ4ANY.mjs} +2 -2
  21. package/dist/lib/browser/chunk-XAKZ4ANY.mjs.map +7 -0
  22. package/dist/lib/browser/{chunk-VGBZKM3O.mjs → chunk-YBPJCY3F.mjs} +2 -2
  23. package/dist/lib/browser/compute-runtime-B4XJVY4Y.mjs +113 -0
  24. package/dist/lib/browser/compute-runtime-B4XJVY4Y.mjs.map +7 -0
  25. package/dist/lib/browser/hooks/index.mjs +5 -4
  26. package/dist/lib/browser/index.mjs +19 -10
  27. package/dist/lib/browser/index.mjs.map +3 -3
  28. package/dist/lib/browser/{intent-resolver-D2OHKQRR.mjs → intent-resolver-5HR7M7T6.mjs} +8 -8
  29. package/dist/lib/browser/intent-resolver-5HR7M7T6.mjs.map +7 -0
  30. package/dist/lib/browser/meta.json +1 -1
  31. package/dist/lib/browser/{react-surface-JXFO46V4.mjs → react-surface-TKU2EQ5A.mjs} +6 -5
  32. package/dist/lib/browser/react-surface-TKU2EQ5A.mjs.map +7 -0
  33. package/dist/lib/browser/types/index.mjs +2 -2
  34. package/dist/lib/node-esm/{AutomationPanel-GHK5UG4K.mjs → AutomationPanel-F5CTC6AT.mjs} +4 -4
  35. package/dist/lib/node-esm/{AutomationSettings-XENNIIZM.mjs → AutomationSettings-BQLJIFRT.mjs} +6 -7
  36. package/dist/lib/node-esm/FunctionsContainer-ZHHJPQAZ.mjs +145 -0
  37. package/dist/lib/node-esm/FunctionsContainer-ZHHJPQAZ.mjs.map +7 -0
  38. package/dist/lib/node-esm/{FunctionsPanel-SAMRTELO.mjs → FunctionsPanel-RVVCS6VH.mjs} +3 -3
  39. package/dist/lib/node-esm/{app-graph-builder-TR2WXPX2.mjs → app-graph-builder-SLQOO7GH.mjs} +7 -7
  40. package/dist/lib/node-esm/app-graph-builder-SLQOO7GH.mjs.map +7 -0
  41. package/dist/lib/node-esm/{chunk-LWASMQIF.mjs → chunk-3IYSC75Z.mjs} +5 -5
  42. package/dist/lib/node-esm/chunk-7B6NAAI6.mjs +101 -0
  43. package/dist/lib/node-esm/chunk-7B6NAAI6.mjs.map +7 -0
  44. package/dist/lib/node-esm/{chunk-5MQJPJR2.mjs → chunk-CEVIVRTY.mjs} +6 -2
  45. package/dist/lib/node-esm/chunk-CEVIVRTY.mjs.map +7 -0
  46. package/dist/lib/node-esm/{chunk-EX74SIDO.mjs → chunk-CF2SWXPW.mjs} +26 -42
  47. package/dist/lib/node-esm/chunk-CF2SWXPW.mjs.map +7 -0
  48. package/dist/lib/node-esm/{chunk-CJUI6AKX.mjs → chunk-ECJKIUBO.mjs} +2 -2
  49. package/dist/lib/node-esm/{chunk-3EWMZAU6.mjs → chunk-F76XVENA.mjs} +38 -18
  50. package/dist/lib/node-esm/chunk-F76XVENA.mjs.map +7 -0
  51. package/dist/lib/node-esm/{chunk-WHCSOUNN.mjs → chunk-KB7NFEYY.mjs} +2 -2
  52. package/dist/lib/node-esm/chunk-KB7NFEYY.mjs.map +7 -0
  53. package/dist/lib/node-esm/{chunk-YQXW3JXD.mjs → chunk-RVK52XGK.mjs} +27 -12
  54. package/dist/lib/node-esm/chunk-RVK52XGK.mjs.map +7 -0
  55. package/dist/lib/node-esm/compute-runtime-JUWTQXOV.mjs +114 -0
  56. package/dist/lib/node-esm/compute-runtime-JUWTQXOV.mjs.map +7 -0
  57. package/dist/lib/node-esm/hooks/index.mjs +5 -4
  58. package/dist/lib/node-esm/index.mjs +19 -10
  59. package/dist/lib/node-esm/index.mjs.map +3 -3
  60. package/dist/lib/node-esm/{intent-resolver-2LGBVXT5.mjs → intent-resolver-KDRYB5BC.mjs} +8 -8
  61. package/dist/lib/node-esm/intent-resolver-KDRYB5BC.mjs.map +7 -0
  62. package/dist/lib/node-esm/meta.json +1 -1
  63. package/dist/lib/node-esm/{react-surface-YDJ43B3N.mjs → react-surface-7QROSEGH.mjs} +6 -5
  64. package/dist/lib/node-esm/react-surface-7QROSEGH.mjs.map +7 -0
  65. package/dist/lib/node-esm/types/index.mjs +2 -2
  66. package/dist/types/src/capabilities/capabilities.d.ts +2 -1
  67. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  68. package/dist/types/src/capabilities/compute-runtime.d.ts.map +1 -1
  69. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  70. package/dist/types/src/components/AutomationPanel/AutomationPanel.d.ts +2 -2
  71. package/dist/types/src/components/AutomationPanel/AutomationPanel.d.ts.map +1 -1
  72. package/dist/types/src/components/AutomationPanel/AutomationPanel.stories.d.ts +3 -0
  73. package/dist/types/src/components/AutomationPanel/AutomationPanel.stories.d.ts.map +1 -1
  74. package/dist/types/src/components/FunctionsContainer.d.ts.map +1 -1
  75. package/dist/types/src/components/FunctionsPanel/FunctionsPanel.d.ts.map +1 -1
  76. package/dist/types/src/components/FunctionsRegistry/FunctionsRegistry.d.ts +8 -0
  77. package/dist/types/src/components/FunctionsRegistry/FunctionsRegistry.d.ts.map +1 -0
  78. package/dist/types/src/components/FunctionsRegistry/index.d.ts +2 -0
  79. package/dist/types/src/components/FunctionsRegistry/index.d.ts.map +1 -0
  80. package/dist/types/src/components/TriggerEditor/FunctionInputEditor.d.ts +2 -2
  81. package/dist/types/src/components/TriggerEditor/FunctionInputEditor.d.ts.map +1 -1
  82. package/dist/types/src/components/TriggerEditor/SpecSelector.d.ts.map +1 -1
  83. package/dist/types/src/components/TriggerEditor/TriggerEditor.d.ts +6 -5
  84. package/dist/types/src/components/TriggerEditor/TriggerEditor.d.ts.map +1 -1
  85. package/dist/types/src/components/TriggerEditor/TriggerEditor.stories.d.ts +3 -1
  86. package/dist/types/src/components/TriggerEditor/TriggerEditor.stories.d.ts.map +1 -1
  87. package/dist/types/src/hooks/useComputeRuntimeCallback.d.ts +5 -1
  88. package/dist/types/src/hooks/useComputeRuntimeCallback.d.ts.map +1 -1
  89. package/dist/types/src/hooks/useTriggerRuntimeControls.d.ts +2 -2
  90. package/dist/types/src/hooks/useTriggerRuntimeControls.d.ts.map +1 -1
  91. package/dist/types/src/index.d.ts +1 -0
  92. package/dist/types/src/index.d.ts.map +1 -1
  93. package/dist/types/src/meta.d.ts.map +1 -1
  94. package/dist/types/src/testing/test-functions.d.ts +2 -3
  95. package/dist/types/src/testing/test-functions.d.ts.map +1 -1
  96. package/dist/types/src/translations.d.ts +3 -0
  97. package/dist/types/src/translations.d.ts.map +1 -1
  98. package/dist/types/tsconfig.tsbuildinfo +1 -1
  99. package/package.json +36 -33
  100. package/src/AutomationPlugin.tsx +2 -2
  101. package/src/capabilities/app-graph-builder.ts +6 -6
  102. package/src/capabilities/capabilities.ts +2 -9
  103. package/src/capabilities/compute-runtime.ts +27 -36
  104. package/src/capabilities/intent-resolver.ts +5 -5
  105. package/src/capabilities/react-surface.tsx +2 -1
  106. package/src/components/AutomationPanel/AutomationPanel.stories.tsx +4 -5
  107. package/src/components/AutomationPanel/AutomationPanel.tsx +48 -35
  108. package/src/components/FunctionsContainer.tsx +7 -0
  109. package/src/components/FunctionsPanel/FunctionsPanel.tsx +32 -13
  110. package/src/components/FunctionsRegistry/FunctionsRegistry.tsx +127 -0
  111. package/src/components/FunctionsRegistry/index.ts +5 -0
  112. package/src/components/TriggerEditor/FunctionInputEditor.tsx +2 -2
  113. package/src/components/TriggerEditor/SpecSelector.tsx +4 -5
  114. package/src/components/TriggerEditor/TriggerEditor.stories.tsx +37 -16
  115. package/src/components/TriggerEditor/TriggerEditor.tsx +24 -54
  116. package/src/hooks/useComputeRuntimeCallback.ts +40 -2
  117. package/src/hooks/useTriggerRuntimeControls.ts +4 -3
  118. package/src/index.ts +1 -0
  119. package/src/meta.ts +5 -2
  120. package/src/testing/test-functions.ts +2 -2
  121. package/src/translations.ts +3 -0
  122. package/dist/lib/browser/AutomationPanel-PNBH5L5C.mjs +0 -11
  123. package/dist/lib/browser/FunctionsContainer-HHBMPUOD.mjs +0 -36
  124. package/dist/lib/browser/FunctionsContainer-HHBMPUOD.mjs.map +0 -7
  125. package/dist/lib/browser/app-graph-builder-DV5HMFX4.mjs.map +0 -7
  126. package/dist/lib/browser/chunk-5ARH77PV.mjs.map +0 -7
  127. package/dist/lib/browser/chunk-DLLE4FKP.mjs.map +0 -7
  128. package/dist/lib/browser/chunk-LC3QQU47.mjs.map +0 -7
  129. package/dist/lib/browser/chunk-MVPRI3DB.mjs +0 -53
  130. package/dist/lib/browser/chunk-MVPRI3DB.mjs.map +0 -7
  131. package/dist/lib/browser/chunk-NIJWEQRD.mjs.map +0 -7
  132. package/dist/lib/browser/chunk-QT3YWUOT.mjs +0 -14
  133. package/dist/lib/browser/chunk-QT3YWUOT.mjs.map +0 -7
  134. package/dist/lib/browser/chunk-WWURMV25.mjs +0 -13
  135. package/dist/lib/browser/chunk-WWURMV25.mjs.map +0 -7
  136. package/dist/lib/browser/compute-runtime-YJREH6WP.mjs +0 -160
  137. package/dist/lib/browser/compute-runtime-YJREH6WP.mjs.map +0 -7
  138. package/dist/lib/browser/intent-resolver-D2OHKQRR.mjs.map +0 -7
  139. package/dist/lib/browser/react-surface-JXFO46V4.mjs.map +0 -7
  140. package/dist/lib/node-esm/FunctionsContainer-ZKVOBUHV.mjs +0 -37
  141. package/dist/lib/node-esm/FunctionsContainer-ZKVOBUHV.mjs.map +0 -7
  142. package/dist/lib/node-esm/app-graph-builder-TR2WXPX2.mjs.map +0 -7
  143. package/dist/lib/node-esm/chunk-3EWMZAU6.mjs.map +0 -7
  144. package/dist/lib/node-esm/chunk-5MQJPJR2.mjs.map +0 -7
  145. package/dist/lib/node-esm/chunk-EX74SIDO.mjs.map +0 -7
  146. package/dist/lib/node-esm/chunk-QCA543ZR.mjs +0 -54
  147. package/dist/lib/node-esm/chunk-QCA543ZR.mjs.map +0 -7
  148. package/dist/lib/node-esm/chunk-U7LJXQS4.mjs +0 -16
  149. package/dist/lib/node-esm/chunk-U7LJXQS4.mjs.map +0 -7
  150. package/dist/lib/node-esm/chunk-WHCSOUNN.mjs.map +0 -7
  151. package/dist/lib/node-esm/chunk-YQXW3JXD.mjs.map +0 -7
  152. package/dist/lib/node-esm/compute-runtime-CMEPAYND.mjs +0 -161
  153. package/dist/lib/node-esm/compute-runtime-CMEPAYND.mjs.map +0 -7
  154. package/dist/lib/node-esm/intent-resolver-2LGBVXT5.mjs.map +0 -7
  155. package/dist/lib/node-esm/react-surface-YDJ43B3N.mjs.map +0 -7
  156. /package/dist/lib/browser/{AutomationPanel-PNBH5L5C.mjs.map → AutomationPanel-M4M77L4V.mjs.map} +0 -0
  157. /package/dist/lib/browser/{FunctionsPanel-NRIKAPQV.mjs.map → FunctionsPanel-CRW6SJUN.mjs.map} +0 -0
  158. /package/dist/lib/browser/{chunk-AY67OUDA.mjs.map → chunk-CZVA5NMD.mjs.map} +0 -0
  159. /package/dist/lib/browser/{chunk-VGBZKM3O.mjs.map → chunk-YBPJCY3F.mjs.map} +0 -0
  160. /package/dist/lib/node-esm/{AutomationPanel-GHK5UG4K.mjs.map → AutomationPanel-F5CTC6AT.mjs.map} +0 -0
  161. /package/dist/lib/{browser/AutomationSettings-3LABN6ER.mjs.map → node-esm/AutomationSettings-BQLJIFRT.mjs.map} +0 -0
  162. /package/dist/lib/node-esm/{FunctionsPanel-SAMRTELO.mjs.map → FunctionsPanel-RVVCS6VH.mjs.map} +0 -0
  163. /package/dist/lib/node-esm/{chunk-LWASMQIF.mjs.map → chunk-3IYSC75Z.mjs.map} +0 -0
  164. /package/dist/lib/node-esm/{chunk-CJUI6AKX.mjs.map → chunk-ECJKIUBO.mjs.map} +0 -0
@@ -5,10 +5,13 @@
5
5
  import * as Schema from 'effect/Schema';
6
6
  import React, { useCallback, useMemo } from 'react';
7
7
 
8
- import { LayoutAction, createIntent, useIntentDispatcher } from '@dxos/app-framework';
9
- import { FunctionType, ScriptType } from '@dxos/functions';
10
- import { Filter, type Space, fullyQualifiedId, useQuery } from '@dxos/react-client/echo';
11
- import { Button, useTranslation } from '@dxos/react-ui';
8
+ import { LayoutAction, createIntent } from '@dxos/app-framework';
9
+ import { useIntentDispatcher } from '@dxos/app-framework/react';
10
+ import { Obj } from '@dxos/echo';
11
+ import { Function, Script } from '@dxos/functions';
12
+ import { SpaceAction } from '@dxos/plugin-space/types';
13
+ import { Filter, type Space, useQuery } from '@dxos/react-client/echo';
14
+ import { Button, IconButton, useTranslation } from '@dxos/react-ui';
12
15
  import { controlItemClasses } from '@dxos/react-ui-form';
13
16
  import { List } from '@dxos/react-ui-list';
14
17
  import { ghostHover, mx } from '@dxos/react-ui-theme';
@@ -23,8 +26,8 @@ export type FunctionsPanelProps = {
23
26
 
24
27
  export const FunctionsPanel = ({ space }: FunctionsPanelProps) => {
25
28
  const { t } = useTranslation(meta.id);
26
- const functions = useQuery(space, Filter.type(FunctionType));
27
- const scripts = useQuery(space, Filter.type(ScriptType));
29
+ const functions = useQuery(space, Filter.type(Function.Function));
30
+ const scripts = useQuery(space, Filter.type(Script.Script));
28
31
  const { dispatchPromise: dispatch } = useIntentDispatcher();
29
32
 
30
33
  const functionToScriptMap = useMemo(
@@ -40,13 +43,13 @@ export const FunctionsPanel = ({ space }: FunctionsPanelProps) => {
40
43
  }
41
44
  return map;
42
45
  },
43
- {} as Record<string, ScriptType>,
46
+ {} as Record<string, Script.Script>,
44
47
  ),
45
48
  [functions, scripts],
46
49
  );
47
50
 
48
51
  const getScriptName = useCallback(
49
- (func: FunctionType) => {
52
+ (func: Function.Function) => {
50
53
  const script = functionToScriptMap[func.id];
51
54
  return script?.name;
52
55
  },
@@ -54,23 +57,33 @@ export const FunctionsPanel = ({ space }: FunctionsPanelProps) => {
54
57
  );
55
58
 
56
59
  const handleGoToScript = useCallback(
57
- (func: FunctionType) => {
60
+ (func: Function.Function) => {
58
61
  const script = functionToScriptMap[func.id];
59
62
  if (script) {
60
- void dispatch(createIntent(LayoutAction.Open, { part: 'main', subject: [fullyQualifiedId(script)] }));
63
+ void dispatch(
64
+ createIntent(LayoutAction.Open, {
65
+ part: 'main',
66
+ subject: [Obj.getDXN(script).toString()],
67
+ }),
68
+ );
61
69
  }
62
70
  },
63
71
  [functionToScriptMap, dispatch],
64
72
  );
65
73
 
74
+ const handleDelete = useCallback(
75
+ (func: Function.Function) => dispatch(createIntent(SpaceAction.RemoveObjects, { objects: [func] })),
76
+ [dispatch],
77
+ );
78
+
66
79
  return (
67
80
  <div role='none' className={mx(controlItemClasses)}>
68
81
  {functions.length > 0 && (
69
- <List.Root<FunctionType> items={functions} isItem={Schema.is(FunctionType)} getId={(func) => func.id}>
82
+ <List.Root<Function.Function> items={functions} isItem={Schema.is(Function.Function)} getId={(func) => func.id}>
70
83
  {({ items }) => (
71
- <div role='list' className='flex flex-col w-full'>
84
+ <div role='list' className='flex flex-col is-full'>
72
85
  {items?.map((func) => (
73
- <List.Item<FunctionType>
86
+ <List.Item<Function.Function>
74
87
  key={func.id}
75
88
  item={func}
76
89
  classNames={mx(grid, ghostHover, 'items-center', 'pli-2', 'min-bs-[3rem]')}
@@ -84,6 +97,12 @@ export const FunctionsPanel = ({ space }: FunctionsPanelProps) => {
84
97
  {functionToScriptMap[func.id] && (
85
98
  <Button onClick={() => handleGoToScript(func)}>{t('go to function source button label')}</Button>
86
99
  )}
100
+ <IconButton
101
+ iconOnly
102
+ icon='ph--trash--regular'
103
+ label={t('delete function button label')}
104
+ onClick={() => handleDelete(func)}
105
+ />
87
106
  </List.Item>
88
107
  ))}
89
108
  </div>
@@ -0,0 +1,127 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Array from 'effect/Array';
6
+ import * as EffectFunction from 'effect/Function';
7
+ import * as Order from 'effect/Order';
8
+ import * as Schema from 'effect/Schema';
9
+ import { useState } from 'react';
10
+ import React, { useCallback } from 'react';
11
+
12
+ import { Function } from '@dxos/functions';
13
+ import { getDeployedFunctions } from '@dxos/functions-runtime/edge';
14
+ import { useClient } from '@dxos/react-client';
15
+ import { Filter, Query, type Space, useQuery } from '@dxos/react-client/echo';
16
+ import { useAsyncEffect } from '@dxos/react-ui';
17
+ import { IconButton, useTranslation } from '@dxos/react-ui';
18
+ import { controlItemClasses } from '@dxos/react-ui-form';
19
+ import { List } from '@dxos/react-ui-list';
20
+ import { ghostHover, mx } from '@dxos/react-ui-theme';
21
+
22
+ import { meta } from '../../meta';
23
+
24
+ const grid = 'grid grid-cols-[1fr_1fr_auto] min-bs-[2.5rem]';
25
+
26
+ type FunctionsRegistryProps = {
27
+ space: Space;
28
+ };
29
+
30
+ export const FunctionsRegistry = ({ space }: FunctionsRegistryProps) => {
31
+ const client = useClient();
32
+ const [loading, setLoading] = useState(true);
33
+ const [functions, setFunctions] = useState<Function.Function[]>([]);
34
+ const { t } = useTranslation(meta.id);
35
+
36
+ const dbFunctions = useQuery(space, Filter.type(Function.Function));
37
+
38
+ const state = (func: Function.Function) => {
39
+ const dbFunction = dbFunctions.find((f) => f.key === func.key);
40
+ if (!dbFunction) {
41
+ return 'import';
42
+ }
43
+ if (dbFunction.version === func.version && dbFunction.updated === func.updated) {
44
+ return 'none';
45
+ }
46
+ return 'update';
47
+ };
48
+
49
+ useAsyncEffect(async () => {
50
+ setLoading(true);
51
+ const functions = await getDeployedFunctions(client);
52
+ setFunctions(functions);
53
+ setLoading(false);
54
+ }, []);
55
+
56
+ const dedupedFunctions = EffectFunction.pipe(
57
+ functions,
58
+ Array.filter((_) => _.key !== undefined),
59
+ Array.sort(Order.reverse(Order.mapInput(Order.string, (_: Function.Function) => _.updated ?? ''))),
60
+ Array.dedupeWith((self, that) => self.key === that.key),
61
+ Array.sort(Order.mapInput(Order.string, (_: Function.Function) => _.key ?? '')),
62
+ );
63
+
64
+ const hanleImportOrUpdate = useCallback(
65
+ async (func: Function.Function) => {
66
+ const {
67
+ objects: [existingFunc],
68
+ } = await space.db.query(Query.type(Function.Function, { key: func.key })).run();
69
+ if (!existingFunc) {
70
+ space.db.add(func);
71
+ return;
72
+ }
73
+ Function.setFrom(existingFunc, func);
74
+ },
75
+ [space],
76
+ );
77
+
78
+ return (
79
+ <div role='none' className={mx(controlItemClasses)}>
80
+ {dedupedFunctions.length > 0 && (
81
+ <List.Root<Function.Function>
82
+ items={dedupedFunctions}
83
+ isItem={Schema.is(Function.Function)}
84
+ getId={(func) => func.id}
85
+ >
86
+ {({ items }) => (
87
+ <div role='list' className='flex flex-col is-full'>
88
+ {items?.map((func) => (
89
+ <List.Item<Function.Function>
90
+ key={func.id}
91
+ item={func}
92
+ classNames={mx(grid, ghostHover, 'items-center', 'pli-2', 'min-bs-[3rem]')}
93
+ >
94
+ <div className='flex flex-col truncate'>
95
+ <List.ItemTitle classNames='truncate'>{func.name}</List.ItemTitle>
96
+ <div className='text-xs text-description truncate'>{func.key}</div>
97
+ </div>
98
+ <div className='flex flex-col truncate'>
99
+ <div className='text-xs text-description truncate'>{func.version}</div>
100
+ <div className='text-xs text-description truncate'>
101
+ {func.updated ? `Uploaded ${new Date(func.updated).toLocaleString()}` : ''}
102
+ </div>
103
+ </div>
104
+
105
+ <IconButton
106
+ iconOnly
107
+ icon={state(func) === 'update' ? 'ph--arrows-clockwise--regular' : 'ph--download--regular'}
108
+ label={
109
+ state(func) === 'update' ? t('update function button label') : t('import function button label')
110
+ }
111
+ disabled={state(func) === 'none'}
112
+ onClick={() => hanleImportOrUpdate(func)}
113
+ />
114
+ </List.Item>
115
+ ))}
116
+ </div>
117
+ )}
118
+ </List.Root>
119
+ )}
120
+
121
+ {dedupedFunctions.length === 0 && !loading && (
122
+ <div className='text-center plb-4 text-gray-500'>{t('no functions found')}</div>
123
+ )}
124
+ {loading && <div className='text-center plb-4 text-gray-500'>{t('loading functions')}</div>}
125
+ </div>
126
+ );
127
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export { FunctionsRegistry } from './FunctionsRegistry';
@@ -6,12 +6,12 @@ import React, { useCallback, useMemo } from 'react';
6
6
 
7
7
  import { Ref, Type } from '@dxos/echo';
8
8
  import { type JsonPath } from '@dxos/echo/internal';
9
- import { type FunctionType } from '@dxos/functions';
9
+ import { type Function } from '@dxos/functions';
10
10
  import { useOnTransition } from '@dxos/react-ui';
11
11
  import { Form, type FormInputStateProps, type QueryRefOptions, useFormValues } from '@dxos/react-ui-form';
12
12
 
13
13
  export type FunctionInputEditorProps = {
14
- functions: FunctionType[];
14
+ functions: Function.Function[];
15
15
  onQueryRefOptions: QueryRefOptions;
16
16
  } & FormInputStateProps;
17
17
 
@@ -5,7 +5,7 @@
5
5
  import React, { useCallback, useMemo } from 'react';
6
6
 
7
7
  import { Filter, Query } from '@dxos/echo';
8
- import { type FunctionTrigger, TriggerKinds, type TriggerType } from '@dxos/functions';
8
+ import { Trigger } from '@dxos/functions';
9
9
  import { useTranslation } from '@dxos/react-ui';
10
10
  import { type InputProps, SelectInput, useInputProps } from '@dxos/react-ui-form';
11
11
 
@@ -15,10 +15,10 @@ export type SpecSelectorProps = InputProps;
15
15
 
16
16
  export const SpecSelector = (props: SpecSelectorProps) => {
17
17
  const { t } = useTranslation(meta.id);
18
- const specProps = useInputProps(['spec' satisfies keyof FunctionTrigger]);
18
+ const specProps = useInputProps(['spec' satisfies keyof Trigger.Trigger]);
19
19
 
20
20
  const handleTypeChange = useCallback(
21
- (_type: any, value: string): TriggerType | undefined => {
21
+ (_type: any, value: string): Trigger.Spec | undefined => {
22
22
  const getDefaultTriggerSpec = (kind: string) => {
23
23
  switch (kind) {
24
24
  case 'timer':
@@ -27,7 +27,6 @@ export const SpecSelector = (props: SpecSelectorProps) => {
27
27
  return {
28
28
  kind: 'subscription',
29
29
  query: {
30
- string: 'Query.select(Filter.nothing())',
31
30
  ast: Query.select(Filter.nothing()).ast,
32
31
  },
33
32
  };
@@ -55,7 +54,7 @@ export const SpecSelector = (props: SpecSelectorProps) => {
55
54
 
56
55
  const options = useMemo(
57
56
  () =>
58
- TriggerKinds.map((kind) => ({
57
+ Trigger.Kinds.map((kind) => ({
59
58
  value: kind,
60
59
  label: t(`trigger type ${kind}`),
61
60
  })),
@@ -5,35 +5,45 @@
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { useState } from 'react';
7
7
 
8
- import { Filter, Obj, Ref } from '@dxos/echo';
9
- import { FunctionTrigger, FunctionType } from '@dxos/functions';
8
+ import { Filter, Obj, Ref, Tag, Type } from '@dxos/echo';
9
+ import { Function } from '@dxos/functions';
10
+ import { Trigger } from '@dxos/functions';
10
11
  import { invariant } from '@dxos/invariant';
11
12
  import { faker } from '@dxos/random';
12
- import { useSpaces } from '@dxos/react-client/echo';
13
- import { ContactType, withClientProvider } from '@dxos/react-client/testing';
13
+ import { useQuery } from '@dxos/react-client/echo';
14
+ import { TestSchema, useClientProvider, withClientProvider } from '@dxos/react-client/testing';
14
15
  import { useAsyncEffect } from '@dxos/react-ui';
15
- import { withTheme } from '@dxos/react-ui/testing';
16
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
17
+ import { Employer, Organization, Person, Project } from '@dxos/types';
16
18
 
17
19
  import { functions } from '../../testing';
18
20
  import { translations } from '../../translations';
19
21
 
20
22
  import { TriggerEditor, type TriggerEditorProps } from './TriggerEditor';
21
23
 
24
+ const types = [
25
+ // TODO(burdon): Get label from annotation.
26
+ { value: Organization.Organization.typename, label: 'Organization' },
27
+ { value: Person.Person.typename, label: 'Person' },
28
+ { value: Type.getTypename(Project.Project), label: 'Project' },
29
+ { value: Employer.Employer.typename, label: 'Employer' },
30
+ ];
31
+
22
32
  const DefaultStory = (props: Partial<TriggerEditorProps>) => {
23
- const spaces = useSpaces();
24
- const space = spaces[1];
25
- const [trigger, setTrigger] = useState<FunctionTrigger>();
33
+ const { space } = useClientProvider();
34
+ const [trigger, setTrigger] = useState<Trigger.Trigger>();
35
+ const tags = useQuery(space, Filter.type(Tag.Tag));
26
36
 
27
37
  useAsyncEffect(async () => {
28
38
  if (!space) {
29
39
  return;
30
40
  }
31
41
 
32
- const result = await space.db.query(Filter.type(FunctionType)).run();
42
+ const result = await space.db.query(Filter.type(Function.Function)).run();
33
43
  const fn = result.objects.find((fn) => fn.name === 'example.com/function/forex');
34
44
  invariant(fn);
35
45
  const trigger = space.db.add(
36
- Obj.make(FunctionTrigger, {
46
+ Trigger.make({
37
47
  function: Ref.make(fn),
38
48
  spec: { kind: 'webhook' },
39
49
  input: {
@@ -50,8 +60,15 @@ const DefaultStory = (props: Partial<TriggerEditorProps>) => {
50
60
  }
51
61
 
52
62
  return (
53
- <div role='none' className='w-[32rem] bs-fit border border-separator rounded-sm'>
54
- <TriggerEditor space={space} trigger={trigger} onSave={(values) => console.log('on save', values)} {...props} />
63
+ <div role='none' className='is-[32rem] bs-fit border border-separator rounded-sm'>
64
+ <TriggerEditor
65
+ space={space}
66
+ trigger={trigger}
67
+ types={types}
68
+ tags={tags}
69
+ onSave={(values) => console.log('on save', values)}
70
+ {...props}
71
+ />
55
72
  </div>
56
73
  );
57
74
  };
@@ -62,17 +79,22 @@ const meta = {
62
79
  render: DefaultStory,
63
80
  decorators: [
64
81
  withTheme,
82
+ withLayout({ container: 'column' }),
65
83
  withClientProvider({
66
84
  createIdentity: true,
67
85
  createSpace: true,
68
- types: [FunctionType, FunctionTrigger, ContactType],
86
+ types: [Tag.Tag, Function.Function, Trigger.Trigger, TestSchema.ContactType],
69
87
  onCreateSpace: ({ space }) => {
88
+ space.db.add(Tag.make({ label: 'Important' }));
89
+ space.db.add(Tag.make({ label: 'Investor' }));
90
+ space.db.add(Tag.make({ label: 'New' }));
91
+
70
92
  for (const fn of functions) {
71
- space.db.add(Obj.make(FunctionType, fn));
93
+ space.db.add(Function.make(fn));
72
94
  }
73
95
  Array.from({ length: 10 }).map(() => {
74
96
  return space.db.add(
75
- Obj.make(ContactType, {
97
+ Obj.make(TestSchema.ContactType, {
76
98
  name: faker.person.fullName(),
77
99
  identifiers: [],
78
100
  }),
@@ -82,7 +104,6 @@ const meta = {
82
104
  }),
83
105
  ],
84
106
  parameters: {
85
- layout: 'column',
86
107
  translations,
87
108
  },
88
109
  } satisfies Meta<typeof DefaultStory>;
@@ -5,41 +5,38 @@
5
5
  import React, { useCallback, useMemo } from 'react';
6
6
 
7
7
  import { ComputeGraph } from '@dxos/conductor';
8
- import { Type } from '@dxos/echo';
9
- import { FunctionTrigger, FunctionType, ScriptType } from '@dxos/functions';
8
+ import { DXN, type Query } from '@dxos/echo';
9
+ import { Function, Script, Trigger } from '@dxos/functions';
10
10
  import { Filter, Ref, type Space, useQuery } from '@dxos/react-client/echo';
11
- import { Input, useDensityContext, useElevationContext, useThemeContext, useTranslation } from '@dxos/react-ui';
12
- import { Editor, createBasicExtensions, createThemeExtensions } from '@dxos/react-ui-editor';
11
+ import { Input } from '@dxos/react-ui';
12
+ import { QueryForm, type QueryFormProps } from '@dxos/react-ui-components';
13
13
  import { type CustomInputMap, Form, InputHeader, SelectInput, useRefQueryLookupHandler } from '@dxos/react-ui-form';
14
- import { inputTheme, mx } from '@dxos/react-ui-theme';
15
-
16
- import { meta } from '../../meta';
17
14
 
18
15
  import { FunctionInputEditor, type FunctionInputEditorProps } from './FunctionInputEditor';
19
16
  import { SpecSelector } from './SpecSelector';
20
17
 
21
18
  export type TriggerEditorProps = {
22
19
  space: Space;
23
- trigger: FunctionTrigger;
20
+ trigger: Trigger.Trigger;
24
21
  // TODO(wittjosiah): This needs to apply to whole spec but currently only applies to spec.kind & spec.query.
25
22
  readonlySpec?: boolean;
26
- onSave?: (trigger: Omit<FunctionTrigger, 'id'>) => void;
23
+ onSave?: (trigger: Omit<Trigger.Trigger, 'id'>) => void;
27
24
  onCancel?: () => void;
28
- };
25
+ } & Pick<QueryFormProps, 'types' | 'tags'>;
29
26
 
30
- export const TriggerEditor = ({ space, trigger, readonlySpec, onSave, onCancel }: TriggerEditorProps) => {
31
- const handleSave = (values: FunctionTrigger) => {
27
+ export const TriggerEditor = ({ space, trigger, readonlySpec, types, tags, onSave, onCancel }: TriggerEditorProps) => {
28
+ const handleSave = ({ id: _, ...values }: Trigger.Trigger) => {
32
29
  onSave?.(values);
33
30
  };
34
31
 
35
32
  const handleRefQueryLookup = useRefQueryLookupHandler({ space });
36
- const Custom = useCustomInputs({ space, readonlySpec, onQueryRefOptions: handleRefQueryLookup });
33
+ const Custom = useCustomInputs({ space, readonlySpec, types, tags, onQueryRefOptions: handleRefQueryLookup });
37
34
 
38
35
  return (
39
36
  <Form
40
37
  outerSpacing={false}
41
38
  Custom={Custom}
42
- schema={FunctionTrigger}
39
+ schema={Trigger.Trigger}
43
40
  values={trigger}
44
41
  onSave={handleSave}
45
42
  onCancel={onCancel}
@@ -48,24 +45,21 @@ export const TriggerEditor = ({ space, trigger, readonlySpec, onSave, onCancel }
48
45
  );
49
46
  };
50
47
 
51
- const useCustomInputs = ({
52
- space,
53
- readonlySpec,
54
- onQueryRefOptions,
55
- }: {
48
+ type UseCustomInputsProps = {
56
49
  space: Space;
57
50
  readonlySpec?: boolean;
58
51
  onQueryRefOptions: FunctionInputEditorProps['onQueryRefOptions'];
59
- }) => {
60
- const { t } = useTranslation(meta.id);
61
- const functions = useQuery(space, Filter.type(FunctionType));
52
+ } & Pick<QueryFormProps, 'types' | 'tags'>;
53
+
54
+ const useCustomInputs = ({ space, readonlySpec, types, tags, onQueryRefOptions }: UseCustomInputsProps) => {
55
+ const functions = useQuery(space, Filter.type(Function.Function));
62
56
  const workflows = useQuery(space, Filter.type(ComputeGraph));
63
- const scripts = useQuery(space, Filter.type(ScriptType));
57
+ const scripts = useQuery(space, Filter.type(Script.Script));
64
58
 
65
59
  return useMemo(
66
60
  (): CustomInputMap => ({
67
61
  // Function selector.
68
- ['function' satisfies keyof FunctionTrigger]: (props) => {
62
+ ['function' satisfies keyof Trigger.Trigger]: (props) => {
69
63
  const getValue = useCallback(() => {
70
64
  const formValue = props.getValue();
71
65
  if (Ref.isRef(formValue)) {
@@ -76,7 +70,7 @@ const useCustomInputs = ({
76
70
 
77
71
  const handleOnValueChange = useCallback(
78
72
  (_type: any, dxnString: string) => {
79
- const dxn = Type.DXN.parse(dxnString);
73
+ const dxn = DXN.parse(dxnString);
80
74
  if (dxn) {
81
75
  const ref = Ref.fromDXN(dxn);
82
76
  props.onValueChange('object', ref);
@@ -101,39 +95,15 @@ const useCustomInputs = ({
101
95
  // TODO(wittjosiah): Copied from ViewEditor.
102
96
  // Query input editor.
103
97
  ['spec.query' as const]: (props) => {
104
- const { themeMode } = useThemeContext();
105
- const density = useDensityContext();
106
- const elevation = useElevationContext();
107
- const value = props.getValue() as any;
108
-
109
- // TODO(wittjosiah): Including props.onValueChange in deps causes infinite loop.
110
98
  const handleChange = useCallback(
111
- (text: string) => props.onValueChange('object', { ...value, string: text }),
112
- [],
113
- );
114
-
115
- const extensions = useMemo(
116
- () => [
117
- createBasicExtensions({ readOnly: readonlySpec, placeholder: t('query placeholder') }),
118
- createThemeExtensions({ themeMode }),
119
- ],
120
- [],
99
+ (query: Query.Any) => props.onValueChange('object', { ast: query.ast }),
100
+ [props.onValueChange],
121
101
  );
122
102
 
123
- // TODO(wittjosiah): This is probably not the right way to do these styles.
124
103
  return (
125
104
  <Input.Root>
126
105
  <InputHeader label={props.label} />
127
- <Editor
128
- classNames={mx(
129
- inputTheme.input({ density, elevation }),
130
- 'flex items-center',
131
- 'focus-within:bg-focusSurface focus-within:border-separator focus-within:hover:bg-focusSurface',
132
- )}
133
- extensions={extensions}
134
- value={value.string}
135
- onChange={handleChange}
136
- />
106
+ <QueryForm initialQuery={(props.getValue() as any).ast} types={types} tags={tags} onChange={handleChange} />
137
107
  </Input.Root>
138
108
  );
139
109
  },
@@ -151,7 +121,7 @@ const getWorkflowOptions = (graphs: ComputeGraph[]) => {
151
121
  return graphs.map((graph) => ({ label: `compute-${graph.id}`, value: `dxn:echo:@:${graph.id}` }));
152
122
  };
153
123
 
154
- const getFunctionOptions = (scripts: ScriptType[], functions: FunctionType[]) => {
155
- const getLabel = (fn: FunctionType) => scripts.find((s) => fn.source?.target?.id === s.id)?.name ?? fn.name;
124
+ const getFunctionOptions = (scripts: Script.Script[], functions: Function.Function[]) => {
125
+ const getLabel = (fn: Function.Function) => scripts.find((s) => fn.source?.target?.id === s.id)?.name ?? fn.name;
156
126
  return functions.map((fn) => ({ label: getLabel(fn), value: `dxn:echo:@:${fn.id}` }));
157
127
  };
@@ -2,10 +2,15 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import type * as Effect from 'effect/Effect';
5
+ import * as Cause from 'effect/Cause';
6
+ import * as Effect from 'effect/Effect';
7
+ import * as Exit from 'effect/Exit';
6
8
  import { useCallback } from 'react';
7
9
 
8
- import { useCapability } from '@dxos/app-framework';
10
+ import { useCapability } from '@dxos/app-framework/react';
11
+ import { type FunctionDefinition, FunctionInvocationService } from '@dxos/functions';
12
+ import { InvocationTracer, TracingServiceExt } from '@dxos/functions-runtime';
13
+ import { log } from '@dxos/log';
9
14
  import type { Space } from '@dxos/react-client/echo';
10
15
 
11
16
  import { AutomationCapabilities } from '../capabilities';
@@ -13,6 +18,7 @@ import { AutomationCapabilities } from '../capabilities';
13
18
  /**
14
19
  * Create an effectful function that has access to compute services
15
20
  */
21
+ // TODO(burdon): Factor out (figure out cross-plugin capabilities dependencies).
16
22
  export const useComputeRuntimeCallback = <T>(
17
23
  space: Space | undefined,
18
24
  fn: () => Effect.Effect<T, any, AutomationCapabilities.ComputeServices>,
@@ -25,6 +31,38 @@ export const useComputeRuntimeCallback = <T>(
25
31
  if (!runtime) {
26
32
  throw new TypeError('Space not provided to useComputeRuntimeCallback');
27
33
  }
34
+
28
35
  return runtime.runPromise(fn());
29
36
  }, [runtime, ...(deps ?? [])]);
30
37
  };
38
+
39
+ // TODO(wittjosiah): Function invoking should automatically be traced (DX-647).
40
+ export const invokeFunctionWithTracing = <I, O>(functionDef: FunctionDefinition<I, O>, inputData: I) =>
41
+ Effect.gen(function* () {
42
+ const tracer = yield* InvocationTracer;
43
+ const trace = yield* tracer.traceInvocationStart({
44
+ target: undefined,
45
+ payload: {
46
+ data: {},
47
+ },
48
+ });
49
+
50
+ // Invoke the function.
51
+ const result = yield* FunctionInvocationService.invokeFunction(functionDef, inputData).pipe(
52
+ Effect.provide(TracingServiceExt.layerQueue(trace.invocationTraceQueue)),
53
+ Effect.exit,
54
+ );
55
+
56
+ if (Exit.isFailure(result)) {
57
+ const error = Cause.prettyErrors(result.cause)[0];
58
+ log.error(error.message, error.cause ?? error.stack);
59
+ }
60
+
61
+ yield* tracer.traceInvocationEnd({
62
+ trace,
63
+ // TODO(dmaretskyi): Might miss errors.
64
+ exception: Exit.isFailure(result) ? Cause.prettyErrors(result.cause)[0] : undefined,
65
+ });
66
+
67
+ return result;
68
+ });
@@ -5,21 +5,22 @@
5
5
  import * as Effect from 'effect/Effect';
6
6
 
7
7
  import { Filter } from '@dxos/echo';
8
- import { FunctionTrigger, TriggerDispatcher } from '@dxos/functions';
8
+ import { Trigger } from '@dxos/functions';
9
+ import { TriggerDispatcher } from '@dxos/functions-runtime';
9
10
  import { type Space, useQuery } from '@dxos/react-client/echo';
10
11
  import { useAsyncState } from '@dxos/react-ui';
11
12
 
12
13
  import { useComputeRuntimeCallback } from './useComputeRuntimeCallback';
13
14
 
14
15
  interface TriggerRuntimeControls {
15
- triggers: FunctionTrigger[];
16
+ triggers: Trigger.Trigger[];
16
17
  isRunning: boolean;
17
18
  start: () => void;
18
19
  stop: () => void;
19
20
  }
20
21
 
21
22
  export const useTriggerRuntimeControls = (space: Space | undefined): TriggerRuntimeControls => {
22
- const triggers = useQuery(space, Filter.type(FunctionTrigger));
23
+ const triggers = useQuery(space, Filter.type(Trigger.Trigger));
23
24
 
24
25
  const [isRunningState, setIsRunningState] = useAsyncState(
25
26
  useComputeRuntimeCallback(space, () => TriggerDispatcher.pipe(Effect.map((t) => t.running))),
package/src/index.ts CHANGED
@@ -6,5 +6,6 @@ export { AutomationCapabilities } from './capabilities';
6
6
  export * from './components';
7
7
  export * from './meta';
8
8
  export * from './events';
9
+ export * from './hooks';
9
10
 
10
11
  export * from './AutomationPlugin';
package/src/meta.ts CHANGED
@@ -3,12 +3,15 @@
3
3
  //
4
4
 
5
5
  import { type PluginMeta } from '@dxos/app-framework';
6
+ import { trim } from '@dxos/util';
6
7
 
7
8
  export const meta: PluginMeta = {
8
9
  id: 'dxos.org/plugin/automation',
9
10
  name: 'Automation',
10
- description:
11
- 'The Automation tab allows you to trigger pre-defined workflows related to the element you are interacting with inside of Composer.',
11
+ description: trim`
12
+ Workflow automation engine that triggers custom actions based on object events and conditions.
13
+ Create automated pipelines that respond to changes and streamline repetitive tasks.
14
+ `,
12
15
  icon: 'ph--robot--regular',
13
16
  source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-automation',
14
17
  };