@dxos/plugin-automation 0.8.4-main.5ea62a8 → 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 (191) hide show
  1. package/dist/lib/browser/AutomationPanel-M4M77L4V.mjs +11 -0
  2. package/dist/lib/browser/AutomationSettings-4HCI6KJR.mjs +68 -0
  3. package/dist/lib/browser/AutomationSettings-4HCI6KJR.mjs.map +7 -0
  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-I443Y6KB.mjs → FunctionsPanel-CRW6SJUN.mjs} +3 -3
  7. package/dist/lib/browser/{app-graph-builder-4OFKIRAI.mjs → app-graph-builder-W7LLC6XW.mjs} +12 -11
  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-S4SM663I.mjs → chunk-7W6QMY3L.mjs} +61 -24
  12. package/dist/lib/browser/chunk-7W6QMY3L.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-CZVA5NMD.mjs +14 -0
  14. package/dist/lib/browser/chunk-CZVA5NMD.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-LZQFZO3B.mjs +17 -0
  16. package/dist/lib/browser/chunk-LZQFZO3B.mjs.map +7 -0
  17. package/dist/lib/browser/{chunk-PUUREQ2B.mjs → chunk-T6ZESHDY.mjs} +75 -27
  18. package/dist/lib/browser/chunk-T6ZESHDY.mjs.map +7 -0
  19. package/dist/lib/browser/{chunk-Z5DT4MHW.mjs → chunk-TWWFNOIR.mjs} +30 -15
  20. package/dist/lib/browser/chunk-TWWFNOIR.mjs.map +7 -0
  21. package/dist/lib/browser/chunk-XAKZ4ANY.mjs +15 -0
  22. package/dist/lib/browser/chunk-XAKZ4ANY.mjs.map +7 -0
  23. package/dist/lib/browser/{chunk-HN7OHFCB.mjs → chunk-YBPJCY3F.mjs} +3 -3
  24. package/dist/lib/browser/chunk-YBPJCY3F.mjs.map +7 -0
  25. package/dist/lib/browser/compute-runtime-B4XJVY4Y.mjs +113 -0
  26. package/dist/lib/browser/compute-runtime-B4XJVY4Y.mjs.map +7 -0
  27. package/dist/lib/browser/hooks/index.mjs +13 -0
  28. package/dist/lib/browser/index.mjs +47 -18
  29. package/dist/lib/browser/index.mjs.map +4 -4
  30. package/dist/lib/browser/{intent-resolver-4PSYSQQG.mjs → intent-resolver-5HR7M7T6.mjs} +10 -10
  31. package/dist/lib/browser/intent-resolver-5HR7M7T6.mjs.map +7 -0
  32. package/dist/lib/browser/meta.json +1 -1
  33. package/dist/lib/browser/{react-surface-N4ZVZH4O.mjs → react-surface-TKU2EQ5A.mjs} +20 -20
  34. package/dist/lib/browser/react-surface-TKU2EQ5A.mjs.map +7 -0
  35. package/dist/lib/browser/types/index.mjs +2 -2
  36. package/dist/lib/node-esm/{AutomationPanel-KUP2555Y.mjs → AutomationPanel-F5CTC6AT.mjs} +4 -4
  37. package/dist/lib/node-esm/AutomationSettings-BQLJIFRT.mjs +69 -0
  38. package/dist/lib/node-esm/AutomationSettings-BQLJIFRT.mjs.map +7 -0
  39. package/dist/lib/node-esm/FunctionsContainer-ZHHJPQAZ.mjs +145 -0
  40. package/dist/lib/node-esm/FunctionsContainer-ZHHJPQAZ.mjs.map +7 -0
  41. package/dist/lib/node-esm/{FunctionsPanel-ELINCXPW.mjs → FunctionsPanel-RVVCS6VH.mjs} +3 -3
  42. package/dist/lib/node-esm/FunctionsPanel-RVVCS6VH.mjs.map +7 -0
  43. package/dist/lib/node-esm/{app-graph-builder-555IHYOB.mjs → app-graph-builder-SLQOO7GH.mjs} +12 -11
  44. package/dist/lib/node-esm/app-graph-builder-SLQOO7GH.mjs.map +7 -0
  45. package/dist/lib/node-esm/chunk-3IYSC75Z.mjs +16 -0
  46. package/dist/lib/node-esm/chunk-3IYSC75Z.mjs.map +7 -0
  47. package/dist/lib/node-esm/chunk-7B6NAAI6.mjs +101 -0
  48. package/dist/lib/node-esm/chunk-7B6NAAI6.mjs.map +7 -0
  49. package/dist/lib/node-esm/chunk-CEVIVRTY.mjs +19 -0
  50. package/dist/lib/node-esm/chunk-CEVIVRTY.mjs.map +7 -0
  51. package/dist/lib/node-esm/{chunk-ZOJVKPCA.mjs → chunk-CF2SWXPW.mjs} +61 -24
  52. package/dist/lib/node-esm/chunk-CF2SWXPW.mjs.map +7 -0
  53. package/dist/lib/node-esm/{chunk-OEZNHUL2.mjs → chunk-ECJKIUBO.mjs} +3 -3
  54. package/dist/lib/node-esm/chunk-ECJKIUBO.mjs.map +7 -0
  55. package/dist/lib/node-esm/{chunk-JU7JA5X4.mjs → chunk-F76XVENA.mjs} +75 -27
  56. package/dist/lib/node-esm/chunk-F76XVENA.mjs.map +7 -0
  57. package/dist/lib/node-esm/chunk-KB7NFEYY.mjs +16 -0
  58. package/dist/lib/node-esm/chunk-KB7NFEYY.mjs.map +7 -0
  59. package/dist/lib/node-esm/{chunk-CX2AILIS.mjs → chunk-RVK52XGK.mjs} +30 -15
  60. package/dist/lib/node-esm/chunk-RVK52XGK.mjs.map +7 -0
  61. package/dist/lib/node-esm/compute-runtime-JUWTQXOV.mjs +114 -0
  62. package/dist/lib/node-esm/compute-runtime-JUWTQXOV.mjs.map +7 -0
  63. package/dist/lib/node-esm/hooks/index.mjs +14 -0
  64. package/dist/lib/node-esm/hooks/index.mjs.map +7 -0
  65. package/dist/lib/node-esm/index.mjs +47 -18
  66. package/dist/lib/node-esm/index.mjs.map +4 -4
  67. package/dist/lib/node-esm/{intent-resolver-6ZGBUILG.mjs → intent-resolver-KDRYB5BC.mjs} +10 -10
  68. package/dist/lib/node-esm/intent-resolver-KDRYB5BC.mjs.map +7 -0
  69. package/dist/lib/node-esm/meta.json +1 -1
  70. package/dist/lib/node-esm/{react-surface-XE7ILYWA.mjs → react-surface-7QROSEGH.mjs} +20 -20
  71. package/dist/lib/node-esm/react-surface-7QROSEGH.mjs.map +7 -0
  72. package/dist/lib/node-esm/types/index.mjs +2 -2
  73. package/dist/types/src/AutomationPlugin.d.ts +1 -1
  74. package/dist/types/src/AutomationPlugin.d.ts.map +1 -1
  75. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  76. package/dist/types/src/capabilities/capabilities.d.ts +20 -0
  77. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -0
  78. package/dist/types/src/capabilities/compute-runtime.d.ts +5 -0
  79. package/dist/types/src/capabilities/compute-runtime.d.ts.map +1 -0
  80. package/dist/types/src/capabilities/index.d.ts +2 -0
  81. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  82. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  83. package/dist/types/src/components/AutomationPanel/AutomationPanel.d.ts +6 -5
  84. package/dist/types/src/components/AutomationPanel/AutomationPanel.d.ts.map +1 -1
  85. package/dist/types/src/components/AutomationPanel/AutomationPanel.stories.d.ts +5 -1
  86. package/dist/types/src/components/AutomationPanel/AutomationPanel.stories.d.ts.map +1 -1
  87. package/dist/types/src/components/AutomationSettings.d.ts +5 -0
  88. package/dist/types/src/components/AutomationSettings.d.ts.map +1 -0
  89. package/dist/types/src/components/FunctionsContainer.d.ts.map +1 -1
  90. package/dist/types/src/components/FunctionsPanel/FunctionsPanel.d.ts.map +1 -1
  91. package/dist/types/src/components/FunctionsRegistry/FunctionsRegistry.d.ts +8 -0
  92. package/dist/types/src/components/FunctionsRegistry/FunctionsRegistry.d.ts.map +1 -0
  93. package/dist/types/src/components/FunctionsRegistry/index.d.ts +2 -0
  94. package/dist/types/src/components/FunctionsRegistry/index.d.ts.map +1 -0
  95. package/dist/types/src/components/TriggerEditor/FunctionInputEditor.d.ts +2 -2
  96. package/dist/types/src/components/TriggerEditor/FunctionInputEditor.d.ts.map +1 -1
  97. package/dist/types/src/components/TriggerEditor/SpecSelector.d.ts.map +1 -1
  98. package/dist/types/src/components/TriggerEditor/TriggerEditor.d.ts +7 -5
  99. package/dist/types/src/components/TriggerEditor/TriggerEditor.d.ts.map +1 -1
  100. package/dist/types/src/components/TriggerEditor/TriggerEditor.stories.d.ts +9 -4
  101. package/dist/types/src/components/TriggerEditor/TriggerEditor.stories.d.ts.map +1 -1
  102. package/dist/types/src/components/TriggerSettings.d.ts +6 -0
  103. package/dist/types/src/components/TriggerSettings.d.ts.map +1 -0
  104. package/dist/types/src/components/index.d.ts +2 -2
  105. package/dist/types/src/components/index.d.ts.map +1 -1
  106. package/dist/types/src/events.d.ts +4 -0
  107. package/dist/types/src/events.d.ts.map +1 -0
  108. package/dist/types/src/hooks/index.d.ts +3 -0
  109. package/dist/types/src/hooks/index.d.ts.map +1 -0
  110. package/dist/types/src/hooks/useComputeRuntimeCallback.d.ts +12 -0
  111. package/dist/types/src/hooks/useComputeRuntimeCallback.d.ts.map +1 -0
  112. package/dist/types/src/hooks/useTriggerRuntimeControls.d.ts +11 -0
  113. package/dist/types/src/hooks/useTriggerRuntimeControls.d.ts.map +1 -0
  114. package/dist/types/src/index.d.ts +3 -0
  115. package/dist/types/src/index.d.ts.map +1 -1
  116. package/dist/types/src/meta.d.ts +0 -1
  117. package/dist/types/src/meta.d.ts.map +1 -1
  118. package/dist/types/src/testing/test-functions.d.ts +2 -3
  119. package/dist/types/src/testing/test-functions.d.ts.map +1 -1
  120. package/dist/types/src/translations.d.ts +5 -0
  121. package/dist/types/src/translations.d.ts.map +1 -1
  122. package/dist/types/src/types/schema.d.ts +1 -1
  123. package/dist/types/src/types/schema.d.ts.map +1 -1
  124. package/dist/types/tsconfig.tsbuildinfo +1 -1
  125. package/package.json +61 -38
  126. package/src/AutomationPlugin.tsx +36 -30
  127. package/src/capabilities/app-graph-builder.ts +14 -13
  128. package/src/capabilities/capabilities.ts +41 -0
  129. package/src/capabilities/compute-runtime.ts +129 -0
  130. package/src/capabilities/index.ts +3 -0
  131. package/src/capabilities/intent-resolver.ts +5 -5
  132. package/src/capabilities/react-surface.tsx +15 -15
  133. package/src/components/AutomationPanel/AutomationPanel.stories.tsx +7 -11
  134. package/src/components/AutomationPanel/AutomationPanel.tsx +109 -38
  135. package/src/components/{AutomationContainer.tsx → AutomationSettings.tsx} +8 -6
  136. package/src/components/FunctionsContainer.tsx +11 -4
  137. package/src/components/FunctionsPanel/FunctionsPanel.tsx +35 -16
  138. package/src/components/FunctionsRegistry/FunctionsRegistry.tsx +127 -0
  139. package/src/components/FunctionsRegistry/index.ts +5 -0
  140. package/src/components/TriggerEditor/FunctionInputEditor.tsx +10 -4
  141. package/src/components/TriggerEditor/SpecSelector.tsx +14 -8
  142. package/src/components/TriggerEditor/TriggerEditor.stories.tsx +63 -23
  143. package/src/components/TriggerEditor/TriggerEditor.tsx +45 -19
  144. package/src/components/TriggerSettings.tsx +25 -0
  145. package/src/components/index.ts +1 -1
  146. package/src/events.ts +11 -0
  147. package/src/hooks/index.ts +6 -0
  148. package/src/hooks/useComputeRuntimeCallback.ts +68 -0
  149. package/src/hooks/useTriggerRuntimeControls.ts +53 -0
  150. package/src/index.ts +3 -0
  151. package/src/meta.ts +6 -5
  152. package/src/testing/test-functions.ts +3 -3
  153. package/src/translations.ts +7 -0
  154. package/src/types/schema.ts +1 -1
  155. package/dist/lib/browser/AutomationContainer-A46DB774.mjs +0 -35
  156. package/dist/lib/browser/AutomationContainer-A46DB774.mjs.map +0 -7
  157. package/dist/lib/browser/AutomationPanel-TQXXZHLR.mjs +0 -11
  158. package/dist/lib/browser/FunctionsContainer-U4HASI4P.mjs +0 -36
  159. package/dist/lib/browser/FunctionsContainer-U4HASI4P.mjs.map +0 -7
  160. package/dist/lib/browser/app-graph-builder-4OFKIRAI.mjs.map +0 -7
  161. package/dist/lib/browser/chunk-GW5U2DGT.mjs +0 -15
  162. package/dist/lib/browser/chunk-GW5U2DGT.mjs.map +0 -7
  163. package/dist/lib/browser/chunk-HN7OHFCB.mjs.map +0 -7
  164. package/dist/lib/browser/chunk-IAUUJ5RL.mjs +0 -14
  165. package/dist/lib/browser/chunk-IAUUJ5RL.mjs.map +0 -7
  166. package/dist/lib/browser/chunk-PUUREQ2B.mjs.map +0 -7
  167. package/dist/lib/browser/chunk-S4SM663I.mjs.map +0 -7
  168. package/dist/lib/browser/chunk-Z5DT4MHW.mjs.map +0 -7
  169. package/dist/lib/browser/intent-resolver-4PSYSQQG.mjs.map +0 -7
  170. package/dist/lib/browser/react-surface-N4ZVZH4O.mjs.map +0 -7
  171. package/dist/lib/node-esm/AutomationContainer-5S3BBZVY.mjs +0 -36
  172. package/dist/lib/node-esm/AutomationContainer-5S3BBZVY.mjs.map +0 -7
  173. package/dist/lib/node-esm/FunctionsContainer-VZIVURH6.mjs +0 -37
  174. package/dist/lib/node-esm/FunctionsContainer-VZIVURH6.mjs.map +0 -7
  175. package/dist/lib/node-esm/app-graph-builder-555IHYOB.mjs.map +0 -7
  176. package/dist/lib/node-esm/chunk-7XZGYNT6.mjs +0 -16
  177. package/dist/lib/node-esm/chunk-7XZGYNT6.mjs.map +0 -7
  178. package/dist/lib/node-esm/chunk-CX2AILIS.mjs.map +0 -7
  179. package/dist/lib/node-esm/chunk-JU7JA5X4.mjs.map +0 -7
  180. package/dist/lib/node-esm/chunk-NK5N3QKD.mjs +0 -17
  181. package/dist/lib/node-esm/chunk-NK5N3QKD.mjs.map +0 -7
  182. package/dist/lib/node-esm/chunk-OEZNHUL2.mjs.map +0 -7
  183. package/dist/lib/node-esm/chunk-ZOJVKPCA.mjs.map +0 -7
  184. package/dist/lib/node-esm/intent-resolver-6ZGBUILG.mjs.map +0 -7
  185. package/dist/lib/node-esm/react-surface-XE7ILYWA.mjs.map +0 -7
  186. package/dist/types/src/components/AutomationContainer.d.ts +0 -5
  187. package/dist/types/src/components/AutomationContainer.d.ts.map +0 -1
  188. /package/dist/lib/browser/{AutomationPanel-TQXXZHLR.mjs.map → AutomationPanel-M4M77L4V.mjs.map} +0 -0
  189. /package/dist/lib/browser/{FunctionsPanel-I443Y6KB.mjs.map → FunctionsPanel-CRW6SJUN.mjs.map} +0 -0
  190. /package/dist/lib/{node-esm/AutomationPanel-KUP2555Y.mjs.map → browser/hooks/index.mjs.map} +0 -0
  191. /package/dist/lib/node-esm/{FunctionsPanel-ELINCXPW.mjs.map → AutomationPanel-F5CTC6AT.mjs.map} +0 -0
@@ -2,52 +2,69 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
6
- import React, { useState } from 'react';
7
-
8
- import { Filter, Obj } from '@dxos/echo';
9
- import { FunctionTrigger, FunctionType, ScriptType } from '@dxos/functions';
5
+ import * as Array from 'effect/Array';
6
+ import * as EFn from 'effect/Function';
7
+ import * as Match from 'effect/Match';
8
+ import * as Schema from 'effect/Schema';
9
+ import React, { useMemo, useState } from 'react';
10
+
11
+ import { Filter, Obj, Tag } from '@dxos/echo';
12
+ import { Function, Script, Trigger } from '@dxos/functions';
13
+ import { useTypeOptions } from '@dxos/plugin-space';
10
14
  import { type Client, useClient } from '@dxos/react-client';
11
15
  import { type Space, getSpace, useQuery } from '@dxos/react-client/echo';
12
- import { Clipboard, IconButton, Input, Separator, useTranslation } from '@dxos/react-ui';
16
+ import { Clipboard, IconButton, Input, Separator, type ThemedClassName, useTranslation } from '@dxos/react-ui';
13
17
  import { ControlItem, controlItemClasses } from '@dxos/react-ui-form';
14
18
  import { List } from '@dxos/react-ui-list';
15
19
  import { ghostHover, mx } from '@dxos/react-ui-theme';
20
+ import { Project } from '@dxos/types';
21
+ import { isNonNullable } from '@dxos/util';
16
22
 
17
- import { AUTOMATION_PLUGIN } from '../../meta';
23
+ import { meta } from '../../meta';
18
24
  import { TriggerEditor, type TriggerEditorProps } from '../TriggerEditor';
19
25
 
20
26
  const grid = 'grid grid-cols-[40px_1fr_32px] min-bs-[2.5rem]';
21
27
 
22
- export type AutomationPanelProps = {
28
+ export type AutomationPanelProps = ThemedClassName<{
23
29
  space: Space;
24
30
  object?: Obj.Any;
25
- initialTrigger?: FunctionTrigger;
31
+ initialTrigger?: Trigger.Trigger;
26
32
  onDone?: () => void;
27
- };
33
+ }>;
28
34
 
29
35
  // TODO(burdon): Factor out common layout with ViewEditor.
30
- export const AutomationPanel = ({ space, object, initialTrigger, onDone }: AutomationPanelProps) => {
31
- const { t } = useTranslation(AUTOMATION_PLUGIN);
36
+ export const AutomationPanel = ({ classNames, space, object, initialTrigger, onDone }: AutomationPanelProps) => {
37
+ const { t } = useTranslation(meta.id);
32
38
  const client = useClient();
33
- const triggers = useQuery(space, Filter.type(FunctionTrigger));
34
- const functions = useQuery(space, Filter.type(FunctionType));
35
- const scripts = useQuery(space, Filter.type(ScriptType));
36
-
37
- const [trigger, setTrigger] = useState<FunctionTrigger | undefined>(initialTrigger);
38
- const [selected, setSelected] = useState<FunctionTrigger>();
39
-
40
- const handleSelect = (trigger: FunctionTrigger) => {
39
+ const functions = useQuery(space, Filter.type(Function.Function));
40
+ const triggers = useQuery(space, Filter.type(Trigger.Trigger));
41
+ const filteredTriggers = useMemo(() => {
42
+ return object ? triggers.filter(triggerMatch(object)) : triggers;
43
+ }, [object, triggers]);
44
+ const tags = useQuery(space, Filter.type(Tag.Tag));
45
+ const types = useTypeOptions({
46
+ space,
47
+ annotation: {
48
+ location: ['database', 'runtime'],
49
+ kind: ['user'],
50
+ registered: ['registered'],
51
+ },
52
+ });
53
+
54
+ const [trigger, setTrigger] = useState<Trigger.Trigger | undefined>(initialTrigger);
55
+ const [selected, setSelected] = useState<Trigger.Trigger>();
56
+
57
+ const handleSelect = (trigger: Trigger.Trigger) => {
41
58
  setTrigger(trigger);
42
59
  setSelected(trigger);
43
60
  };
44
61
 
45
62
  const handleAdd = () => {
46
- setTrigger(Obj.make(FunctionTrigger, {}));
63
+ setTrigger(Trigger.make({}));
47
64
  setSelected(undefined);
48
65
  };
49
66
 
50
- const handleDelete = (trigger: FunctionTrigger) => {
67
+ const handleDelete = (trigger: Trigger.Trigger) => {
51
68
  space.db.remove(trigger);
52
69
  setTrigger(undefined);
53
70
  setSelected(undefined);
@@ -57,7 +74,7 @@ export const AutomationPanel = ({ space, object, initialTrigger, onDone }: Autom
57
74
  if (selected) {
58
75
  Object.assign(selected, trigger);
59
76
  } else {
60
- space.db.add(Obj.make(FunctionTrigger, trigger));
77
+ space.db.add(Trigger.make(trigger));
61
78
  }
62
79
 
63
80
  setTrigger(undefined);
@@ -73,24 +90,36 @@ export const AutomationPanel = ({ space, object, initialTrigger, onDone }: Autom
73
90
  if (trigger) {
74
91
  return (
75
92
  <ControlItem title={t('trigger editor title')}>
76
- <TriggerEditor space={space} trigger={trigger} onSave={handleSave} onCancel={handleCancel} />
93
+ <TriggerEditor
94
+ space={space}
95
+ trigger={trigger}
96
+ readonlySpec={Boolean(object)}
97
+ tags={tags}
98
+ types={types}
99
+ onSave={handleSave}
100
+ onCancel={handleCancel}
101
+ />
77
102
  </ControlItem>
78
103
  );
79
104
  }
80
105
 
81
106
  return (
82
- <div className={controlItemClasses}>
83
- {triggers.length > 0 && (
84
- <List.Root<FunctionTrigger> items={triggers} isItem={Schema.is(FunctionTrigger)} getId={(field) => field.id}>
85
- {({ items: triggers }) => (
86
- <div role='list' className='flex flex-col w-full'>
87
- {triggers?.map((trigger) => {
107
+ <div className={mx(controlItemClasses, classNames)}>
108
+ {filteredTriggers.length > 0 && (
109
+ <List.Root<Trigger.Trigger>
110
+ items={filteredTriggers}
111
+ isItem={Schema.is(Trigger.Trigger)}
112
+ getId={(field) => field.id}
113
+ >
114
+ {({ items: filteredTriggers }) => (
115
+ <div role='list' className='flex flex-col is-full'>
116
+ {filteredTriggers?.map((trigger) => {
88
117
  const copyAction = getCopyAction(client, trigger);
89
118
  return (
90
- <List.Item<FunctionTrigger>
119
+ <List.Item<Trigger.Trigger>
91
120
  key={trigger.id}
92
121
  item={trigger}
93
- classNames={mx(grid, ghostHover, 'items-center', 'px-2')}
122
+ classNames={mx(grid, ghostHover, 'items-center', 'pli-2')}
94
123
  >
95
124
  <Input.Root>
96
125
  <Input.Switch
@@ -101,10 +130,10 @@ export const AutomationPanel = ({ space, object, initialTrigger, onDone }: Autom
101
130
 
102
131
  <div className={'flex'}>
103
132
  <List.ItemTitle
104
- classNames='px-1 cursor-pointer w-0 shrink truncate'
133
+ classNames='pli-1 cursor-pointer is-0 shrink truncate'
105
134
  onClick={() => handleSelect(trigger)}
106
135
  >
107
- {getFunctionName(scripts, functions, trigger) ?? '∅'}
136
+ {getFunctionName(functions, trigger) ?? '∅'}
108
137
  </List.ItemTitle>
109
138
 
110
139
  {/* TODO: a better way to expose copy action */}
@@ -124,13 +153,13 @@ export const AutomationPanel = ({ space, object, initialTrigger, onDone }: Autom
124
153
  )}
125
154
  </List.Root>
126
155
  )}
127
- {triggers.length > 0 && <Separator classNames='mlb-4' />}
156
+ {filteredTriggers.length > 0 && <Separator classNames='mlb-4' />}
128
157
  <IconButton icon='ph--plus--regular' label={t('new trigger label')} onClick={handleAdd} />
129
158
  </div>
130
159
  );
131
160
  };
132
161
 
133
- const getCopyAction = (client: Client, trigger: FunctionTrigger | undefined) => {
162
+ const getCopyAction = (client: Client, trigger: Trigger.Trigger | undefined) => {
134
163
  if (trigger?.spec?.kind === 'email') {
135
164
  return { translationKey: 'trigger copy email', contentProvider: () => `${getSpace(trigger)!.id}@dxos.network` };
136
165
  }
@@ -142,7 +171,7 @@ const getCopyAction = (client: Client, trigger: FunctionTrigger | undefined) =>
142
171
  return undefined;
143
172
  };
144
173
 
145
- const getWebhookUrl = (client: Client, trigger: FunctionTrigger) => {
174
+ const getWebhookUrl = (client: Client, trigger: Trigger.Trigger) => {
146
175
  const spaceId = getSpace(trigger)!.id;
147
176
  const edgeUrl = new URL(client.config.values.runtime!.services!.edge!.url!);
148
177
  const isSecure = edgeUrl.protocol.startsWith('https') || edgeUrl.protocol.startsWith('wss');
@@ -150,10 +179,52 @@ const getWebhookUrl = (client: Client, trigger: FunctionTrigger) => {
150
179
  return new URL(`/webhook/${spaceId}:${trigger.id}`, edgeUrl).toString();
151
180
  };
152
181
 
153
- const getFunctionName = (scripts: ScriptType[], functions: FunctionType[], trigger: FunctionTrigger) => {
182
+ const getFunctionName = (functions: Function.Function[], trigger: Trigger.Trigger) => {
154
183
  // TODO(wittjosiah): Truncation should be done in the UI.
155
184
  // Warning that the List component is currently a can of worms.
156
185
  const shortId = trigger.function && `${trigger.function.dxn.toString().slice(0, 16)}…`;
157
186
  const functionObject = functions.find((fn) => fn === trigger.function?.target);
158
187
  return functionObject?.name ?? shortId;
159
188
  };
189
+
190
+ const scriptMatch = (script: Script.Script) => (trigger: Trigger.Trigger) => {
191
+ const fn = trigger.function?.target;
192
+ if (!Obj.instanceOf(Function.Function, fn)) {
193
+ return false;
194
+ }
195
+
196
+ return fn.source?.target === script;
197
+ };
198
+
199
+ const projectMatch = (project: Project.Project) => {
200
+ const viewQueries = EFn.pipe(
201
+ project.columns,
202
+ Array.map((column) => column.view.target),
203
+ Array.filter(isNonNullable),
204
+ Array.map((view) => Obj.getSnapshot(view).query.ast),
205
+ Array.map((ast) => JSON.stringify(ast)),
206
+ );
207
+
208
+ return (trigger: Trigger.Trigger) => {
209
+ const spec = Obj.getSnapshot(trigger).spec;
210
+ if (spec?.kind !== 'subscription') {
211
+ return false;
212
+ }
213
+
214
+ // TODO(wittjosiah): Implement better way of comparing query ASTs.
215
+ return viewQueries.some((query) => JSON.stringify(spec.query) === query);
216
+ };
217
+ };
218
+
219
+ const triggerMatch = Match.type<Obj.Any>().pipe(
220
+ Match.withReturnType<(trigger: Trigger.Trigger) => boolean>(),
221
+ Match.when(
222
+ (obj) => Obj.instanceOf(Script.Script, obj),
223
+ (obj) => scriptMatch(obj),
224
+ ),
225
+ Match.when(
226
+ (obj) => Obj.instanceOf(Project.Project, obj),
227
+ (obj) => projectMatch(obj),
228
+ ),
229
+ Match.orElse((_obj) => () => true),
230
+ );
@@ -7,22 +7,24 @@ import React from 'react';
7
7
  import { useTranslation } from '@dxos/react-ui';
8
8
  import { ControlPage, ControlSection } from '@dxos/react-ui-form';
9
9
 
10
- import { AUTOMATION_PLUGIN } from '../meta';
10
+ import { meta } from '../meta';
11
11
 
12
12
  import { AutomationPanel, type AutomationPanelProps } from './AutomationPanel';
13
+ import { TriggersSettings } from './TriggerSettings';
13
14
 
14
- export const AutomationContainer = (props: AutomationPanelProps) => {
15
- const { t } = useTranslation(AUTOMATION_PLUGIN);
15
+ export const AutomationSettings = (props: AutomationPanelProps) => {
16
+ const { t } = useTranslation(meta.id);
16
17
  return (
17
18
  <ControlPage>
18
19
  <ControlSection
19
- title={t('automation verbose label', { ns: AUTOMATION_PLUGIN })}
20
- description={t('automation description', { ns: AUTOMATION_PLUGIN })}
20
+ title={t('automation verbose label', { ns: meta.id })}
21
+ description={t('automation description', { ns: meta.id })}
21
22
  >
22
23
  <AutomationPanel {...props} />
24
+ <TriggersSettings space={props.space} />
23
25
  </ControlSection>
24
26
  </ControlPage>
25
27
  );
26
28
  };
27
29
 
28
- export default AutomationContainer;
30
+ export default AutomationSettings;
@@ -8,20 +8,27 @@ import { type Space } from '@dxos/react-client/echo';
8
8
  import { useTranslation } from '@dxos/react-ui';
9
9
  import { ControlPage, ControlSection } from '@dxos/react-ui-form';
10
10
 
11
- import { AUTOMATION_PLUGIN } from '../meta';
11
+ import { meta } from '../meta';
12
12
 
13
13
  import { FunctionsPanel } from './FunctionsPanel';
14
+ import { FunctionsRegistry } from './FunctionsRegistry';
14
15
 
15
16
  export const FunctionsContainer = ({ space }: { space: Space }) => {
16
- const { t } = useTranslation(AUTOMATION_PLUGIN);
17
+ const { t } = useTranslation(meta.id);
17
18
  return (
18
19
  <ControlPage>
19
20
  <ControlSection
20
- title={t('functions verbose label', { ns: AUTOMATION_PLUGIN })}
21
- description={t('functions description', { ns: AUTOMATION_PLUGIN })}
21
+ title={t('functions verbose label', { ns: meta.id })}
22
+ description={t('functions description', { ns: meta.id })}
22
23
  >
23
24
  <FunctionsPanel space={space} />
24
25
  </ControlSection>
26
+ <ControlSection
27
+ title={t('functions registry verbose label', { ns: meta.id })}
28
+ description={t('functions registry description', { ns: meta.id })}
29
+ >
30
+ <FunctionsRegistry space={space} />
31
+ </ControlSection>
25
32
  </ControlPage>
26
33
  );
27
34
  };
@@ -2,18 +2,21 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
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';
15
18
 
16
- import { AUTOMATION_PLUGIN } from '../../meta';
19
+ import { meta } from '../../meta';
17
20
 
18
21
  const grid = 'grid grid-cols-[1fr_auto] min-bs-[2.5rem]';
19
22
 
@@ -22,9 +25,9 @@ export type FunctionsPanelProps = {
22
25
  };
23
26
 
24
27
  export const FunctionsPanel = ({ space }: FunctionsPanelProps) => {
25
- const { t } = useTranslation(AUTOMATION_PLUGIN);
26
- const functions = useQuery(space, Filter.type(FunctionType));
27
- const scripts = useQuery(space, Filter.type(ScriptType));
28
+ const { t } = useTranslation(meta.id);
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';
@@ -5,13 +5,13 @@
5
5
  import React, { useCallback, useMemo } from 'react';
6
6
 
7
7
  import { Ref, Type } from '@dxos/echo';
8
- import { type JsonPath } from '@dxos/echo-schema';
9
- import { type FunctionType } from '@dxos/functions';
8
+ import { type JsonPath } from '@dxos/echo/internal';
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
 
@@ -39,7 +39,13 @@ export const FunctionInputEditor = ({
39
39
  useOnTransition(
40
40
  // Clear function parameter input when the function changes.
41
41
  selectedFunctionValue,
42
- (prevValue) => prevValue !== undefined && prevValue !== selectedFunctionValue,
42
+ (prevValue) => {
43
+ if (!Ref.isRef(prevValue) || !Ref.isRef(selectedFunctionValue)) {
44
+ return false;
45
+ }
46
+
47
+ return prevValue.dxn.toString() !== selectedFunctionValue.dxn.toString();
48
+ },
43
49
  (currValue) => currValue !== undefined,
44
50
  () => onValueChange('object', {}),
45
51
  );
@@ -4,28 +4,34 @@
4
4
 
5
5
  import React, { useCallback, useMemo } from 'react';
6
6
 
7
- import { type FunctionTrigger, TriggerKinds, type TriggerType } from '@dxos/functions';
7
+ import { Filter, Query } from '@dxos/echo';
8
+ import { Trigger } from '@dxos/functions';
8
9
  import { useTranslation } from '@dxos/react-ui';
9
10
  import { type InputProps, SelectInput, useInputProps } from '@dxos/react-ui-form';
10
11
 
11
- import { AUTOMATION_PLUGIN } from '../../meta';
12
+ import { meta } from '../../meta';
12
13
 
13
14
  export type SpecSelectorProps = InputProps;
14
15
 
15
16
  export const SpecSelector = (props: SpecSelectorProps) => {
16
- const { t } = useTranslation(AUTOMATION_PLUGIN);
17
- const specProps = useInputProps(['spec' satisfies keyof FunctionTrigger]);
17
+ const { t } = useTranslation(meta.id);
18
+ const specProps = useInputProps(['spec' satisfies keyof Trigger.Trigger]);
18
19
 
19
20
  const handleTypeChange = useCallback(
20
- (_type: any, value: string): TriggerType | undefined => {
21
+ (_type: any, value: string): Trigger.Spec | undefined => {
21
22
  const getDefaultTriggerSpec = (kind: string) => {
22
23
  switch (kind) {
23
24
  case 'timer':
24
25
  return { kind: 'timer', cron: '' };
25
26
  case 'subscription':
26
- return { kind: 'subscription', filter: {} };
27
+ return {
28
+ kind: 'subscription',
29
+ query: {
30
+ ast: Query.select(Filter.nothing()).ast,
31
+ },
32
+ };
27
33
  case 'queue':
28
- return { kind: 'queue', queue: '' };
34
+ return { kind: 'queue', queue: 'dxn:' };
29
35
  case 'email':
30
36
  return { kind: 'email' };
31
37
  case 'webhook':
@@ -48,7 +54,7 @@ export const SpecSelector = (props: SpecSelectorProps) => {
48
54
 
49
55
  const options = useMemo(
50
56
  () =>
51
- TriggerKinds.map((kind) => ({
57
+ Trigger.Kinds.map((kind) => ({
52
58
  value: kind,
53
59
  label: t(`trigger type ${kind}`),
54
60
  })),