@dxos/plugin-routine 0.10.0 → 0.11.0
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.
- package/PLUGIN.mdl +18 -13
- package/dist/lib/RoutinePlugin.mjs +2 -0
- package/dist/lib/RoutinePlugin.node.mjs +894 -0
- package/dist/lib/RoutinePlugin.node.mjs.map +1 -0
- package/dist/lib/RoutinePlugin.workerd.mjs +20 -0
- package/dist/lib/RoutinePlugin.workerd.mjs.map +1 -0
- package/dist/lib/capabilities/node.mjs +12 -0
- package/dist/lib/capabilities/node.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +14 -0
- package/dist/lib/capabilities.mjs.map +1 -0
- package/dist/lib/chunk-RoutineArticle.mjs +79 -0
- package/dist/lib/chunk-RoutineArticle.mjs.map +1 -0
- package/dist/lib/chunk-RoutineCompanion.mjs +192 -0
- package/dist/lib/chunk-RoutineCompanion.mjs.map +1 -0
- package/dist/lib/chunk-RoutinePlugin.mjs +38 -0
- package/dist/lib/chunk-RoutinePlugin.mjs.map +1 -0
- package/dist/lib/chunk-RoutineSettings.mjs +50 -0
- package/dist/lib/chunk-RoutineSettings.mjs.map +1 -0
- package/dist/lib/chunk-RoutineTraceCompanion.mjs +208 -0
- package/dist/lib/chunk-RoutineTraceCompanion.mjs.map +1 -0
- package/dist/lib/chunk-SkillArticle.mjs +28 -0
- package/dist/lib/chunk-SkillArticle.mjs.map +1 -0
- package/dist/lib/chunk-app-graph-builder.mjs +71 -0
- package/dist/lib/chunk-app-graph-builder.mjs.map +1 -0
- package/dist/lib/chunk-create-object.mjs +23 -0
- package/dist/lib/chunk-create-object.mjs.map +1 -0
- package/dist/lib/chunk-create-routine.mjs +34 -0
- package/dist/lib/chunk-create-routine.mjs.map +1 -0
- package/dist/lib/chunk-create-trigger-from-template.mjs +48 -0
- package/dist/lib/chunk-create-trigger-from-template.mjs.map +1 -0
- package/dist/lib/chunk-layer-specs.mjs +179 -0
- package/dist/lib/chunk-layer-specs.mjs.map +1 -0
- package/dist/lib/{neutral/chunk-HLO4LYXT.mjs → chunk-meta.mjs} +19 -27
- package/dist/lib/chunk-meta.mjs.map +1 -0
- package/dist/lib/chunk-operation-handler.mjs +11 -0
- package/dist/lib/chunk-operation-handler.mjs.map +1 -0
- package/dist/lib/chunk-paths.mjs +11 -0
- package/dist/lib/chunk-paths.mjs.map +1 -0
- package/dist/lib/chunk-react-surface.mjs +64 -0
- package/dist/lib/chunk-react-surface.mjs.map +1 -0
- package/dist/lib/chunk-registry-sync.mjs +104 -0
- package/dist/lib/chunk-registry-sync.mjs.map +1 -0
- package/dist/lib/chunk-run-routine.mjs +57 -0
- package/dist/lib/chunk-run-routine.mjs.map +1 -0
- package/dist/lib/chunk-templates.mjs +37 -0
- package/dist/lib/chunk-templates.mjs.map +1 -0
- package/dist/lib/chunk-trigger-runtime-controller.mjs +73 -0
- package/dist/lib/chunk-trigger-runtime-controller.mjs.map +1 -0
- package/dist/lib/chunk-types.mjs +149 -0
- package/dist/lib/chunk-types.mjs.map +1 -0
- package/dist/lib/chunk-util.mjs +142 -0
- package/dist/lib/chunk-util.mjs.map +1 -0
- package/dist/lib/components.mjs +1645 -0
- package/dist/lib/components.mjs.map +1 -0
- package/dist/lib/containers.mjs +11 -0
- package/dist/lib/containers.mjs.map +1 -0
- package/dist/lib/hooks.mjs +37 -0
- package/dist/lib/hooks.mjs.map +1 -0
- package/dist/lib/index.mjs +5 -0
- package/dist/lib/meta.mjs +2 -0
- package/dist/lib/operations.mjs +7 -0
- package/dist/lib/operations.mjs.map +1 -0
- package/dist/lib/plugin.mjs +9 -0
- package/dist/lib/plugin.mjs.map +1 -0
- package/dist/lib/testing.mjs +2 -0
- package/dist/lib/translations.mjs +110 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/lib/types.mjs +2 -0
- package/dist/types/src/RoutinePlugin.d.ts.map +1 -1
- package/dist/types/src/RoutinePlugin.node.d.ts.map +1 -1
- package/dist/types/src/RoutinePlugin.workerd.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -1
- package/dist/types/src/capabilities/create-object.d.ts +1 -1
- package/dist/types/src/capabilities/index.d.ts +0 -1
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/layer-specs.d.ts +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/registry-sync.d.ts +1 -1
- package/dist/types/src/capabilities/templates.d.ts +1 -1
- package/dist/types/src/capabilities/trigger-runtime-controller.d.ts +1 -1
- package/dist/types/src/commands/trigger/create/{queue.d.ts → feed.d.ts} +2 -2
- package/dist/types/src/commands/trigger/create/feed.d.ts.map +1 -0
- package/dist/types/src/commands/trigger/update/{queue.d.ts → feed.d.ts} +2 -2
- package/dist/types/src/commands/trigger/update/feed.d.ts.map +1 -0
- package/dist/types/src/commands/trigger/util.d.ts +1 -1
- package/dist/types/src/commands/trigger/util.d.ts.map +1 -1
- package/dist/types/src/components/InstructionsEditor/InstructionsEditor.d.ts +7 -5
- package/dist/types/src/components/InstructionsEditor/InstructionsEditor.d.ts.map +1 -1
- package/dist/types/src/components/InstructionsEditor/InstructionsEditor.stories.d.ts +3 -3
- package/dist/types/src/components/InstructionsEditor/InstructionsEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/RoutineCard/RoutineCard.d.ts +9 -0
- package/dist/types/src/components/RoutineCard/RoutineCard.d.ts.map +1 -0
- package/dist/types/src/components/RoutineCard/RoutineCard.stories.d.ts +21 -0
- package/dist/types/src/components/RoutineCard/RoutineCard.stories.d.ts.map +1 -0
- package/dist/types/src/components/RoutineCard/index.d.ts +2 -0
- package/dist/types/src/components/RoutineCard/index.d.ts.map +1 -0
- package/dist/types/src/components/RoutineForm/RoutineForm.d.ts +7 -2
- package/dist/types/src/components/RoutineForm/RoutineForm.d.ts.map +1 -1
- package/dist/types/src/components/Schedule/Schedule.d.ts +2 -26
- package/dist/types/src/components/Schedule/Schedule.d.ts.map +1 -1
- package/dist/types/src/components/Schedule/days.d.ts +24 -0
- package/dist/types/src/components/Schedule/days.d.ts.map +1 -0
- package/dist/types/src/components/Schedule/describe-schedule.d.ts +3 -0
- package/dist/types/src/components/Schedule/describe-schedule.d.ts.map +1 -0
- package/dist/types/src/components/Schedule/index.d.ts +2 -0
- package/dist/types/src/components/Schedule/index.d.ts.map +1 -1
- package/dist/types/src/components/TemplateEditor/TemplateEditor.d.ts +1 -1
- package/dist/types/src/components/TemplateEditor/TemplateEditor.d.ts.map +1 -1
- package/dist/types/src/components/TriggerEditor/TriggerEditor.d.ts +1 -2
- package/dist/types/src/components/TriggerEditor/TriggerEditor.d.ts.map +1 -1
- package/dist/types/src/components/TriggerEditor/TriggerKindSelector.d.ts +5 -2
- package/dist/types/src/components/TriggerEditor/TriggerKindSelector.d.ts.map +1 -1
- package/dist/types/src/components/TriggerEditor/trigger-kind-icon.d.ts +5 -0
- package/dist/types/src/components/TriggerEditor/trigger-kind-icon.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/RoutineArticle/RoutineArticle.d.ts +5 -10
- package/dist/types/src/containers/RoutineArticle/RoutineArticle.d.ts.map +1 -1
- package/dist/types/src/containers/RoutineArticle/RoutineArticle.stories.d.ts +6 -2
- package/dist/types/src/containers/RoutineArticle/RoutineArticle.stories.d.ts.map +1 -1
- package/dist/types/src/containers/RoutineCompanion/RoutineCompanion.d.ts +4 -12
- package/dist/types/src/containers/RoutineCompanion/RoutineCompanion.d.ts.map +1 -1
- package/dist/types/src/containers/RoutineCompanion/RoutineCompanion.stories.d.ts.map +1 -1
- package/dist/types/src/containers/RoutineSettings/RoutineSettings.d.ts +4 -8
- package/dist/types/src/containers/RoutineSettings/RoutineSettings.d.ts.map +1 -1
- package/dist/types/src/containers/RoutineTraceCompanion/RoutineTraceCompanion.d.ts +5 -3
- package/dist/types/src/containers/RoutineTraceCompanion/RoutineTraceCompanion.d.ts.map +1 -1
- package/dist/types/src/containers/RoutineTraceCompanion/RoutineTraceCompanion.stories.d.ts.map +1 -1
- package/dist/types/src/containers/RoutineTraceCompanion/useRoutineRuns.d.ts +1 -1
- package/dist/types/src/containers/RoutineTraceCompanion/useRoutineRuns.d.ts.map +1 -1
- package/dist/types/src/containers/SkillArticle/SkillArticle.d.ts +4 -1
- package/dist/types/src/containers/SkillArticle/SkillArticle.d.ts.map +1 -1
- package/dist/types/src/hooks/useTriggerRuntimeControls.d.ts +1 -1
- package/dist/types/src/hooks/useTriggerRuntimeControls.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/operations/run-routine.d.ts.map +1 -1
- package/dist/types/src/operations/run-routine.test.d.ts +2 -0
- package/dist/types/src/operations/run-routine.test.d.ts.map +1 -0
- package/dist/types/src/paths.d.ts.map +1 -1
- package/dist/types/src/templates/index.d.ts +1 -1
- package/dist/types/src/templates/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-functions.d.ts +30 -6
- package/dist/types/src/testing/test-functions.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/RoutineCapabilities.d.ts +9 -3
- package/dist/types/src/types/RoutineCapabilities.d.ts.map +1 -1
- package/dist/types/src/types/RoutineOperation.d.ts +2 -3
- package/dist/types/src/types/RoutineOperation.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +0 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/routines-for-object.d.ts +6 -3
- package/dist/types/src/util/routines-for-object.d.ts.map +1 -1
- package/dist/types/src/util/wire.d.ts +21 -0
- package/dist/types/src/util/wire.d.ts.map +1 -0
- package/dist/types/src/util/wire.test.d.ts +2 -0
- package/dist/types/src/util/wire.test.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +3 -2
- package/package.json +73 -71
- package/src/RoutinePlugin.node.ts +1 -2
- package/src/RoutinePlugin.tsx +2 -4
- package/src/RoutinePlugin.workerd.ts +1 -2
- package/src/capabilities/app-graph-builder.ts +9 -7
- package/src/capabilities/create-object.ts +2 -2
- package/src/capabilities/index.ts +0 -1
- package/src/capabilities/layer-specs.ts +78 -12
- package/src/capabilities/{react-surface.tsx → react-surface.ts} +18 -22
- package/src/capabilities/trigger-runtime-controller.test.ts +12 -18
- package/src/capabilities/trigger-runtime-controller.ts +22 -25
- package/src/commands/trigger/create/{queue.ts → feed.ts} +1 -1
- package/src/commands/trigger/create/index.ts +2 -2
- package/src/commands/trigger/update/{queue.ts → feed.ts} +1 -1
- package/src/commands/trigger/update/index.ts +2 -2
- package/src/commands/trigger/util.ts +1 -2
- package/src/components/InstructionsEditor/InstructionsEditor.stories.tsx +7 -6
- package/src/components/InstructionsEditor/InstructionsEditor.tsx +31 -19
- package/src/components/RoutineCard/RoutineCard.stories.tsx +95 -0
- package/src/components/RoutineCard/RoutineCard.tsx +60 -0
- package/src/components/{MasterDetail → RoutineCard}/index.ts +1 -1
- package/src/components/RoutineForm/RoutineForm.tsx +60 -52
- package/src/components/Schedule/Schedule.stories.tsx +1 -1
- package/src/components/Schedule/Schedule.tsx +4 -73
- package/src/components/Schedule/days.ts +18 -0
- package/src/components/Schedule/describe-schedule.ts +64 -0
- package/src/components/Schedule/index.ts +2 -0
- package/src/components/TemplateEditor/TemplateEditor.stories.tsx +1 -1
- package/src/components/TemplateEditor/TemplateForm.tsx +1 -1
- package/src/components/TriggerEditor/TriggerEditor.stories.tsx +3 -3
- package/src/components/TriggerEditor/TriggerEditor.tsx +79 -66
- package/src/components/TriggerEditor/TriggerKindSelector.tsx +4 -30
- package/src/components/TriggerEditor/trigger-kind-icon.ts +38 -0
- package/src/components/index.ts +1 -1
- package/src/containers/RoutineArticle/RoutineArticle.stories.tsx +78 -19
- package/src/containers/RoutineArticle/RoutineArticle.tsx +8 -4
- package/src/containers/RoutineCompanion/RoutineCompanion.stories.tsx +5 -4
- package/src/containers/RoutineCompanion/RoutineCompanion.tsx +11 -7
- package/src/containers/RoutineSettings/RoutineSettings.tsx +22 -33
- package/src/containers/RoutineTraceCompanion/RoutineTraceCompanion.stories.tsx +6 -5
- package/src/containers/RoutineTraceCompanion/RoutineTraceCompanion.tsx +3 -1
- package/src/containers/RoutineTraceCompanion/useRoutineRuns.ts +3 -5
- package/src/containers/SkillArticle/SkillArticle.tsx +2 -0
- package/src/hooks/useTriggerRuntimeControls.ts +2 -2
- package/src/index.ts +1 -0
- package/src/operations/create-routine.ts +1 -1
- package/src/operations/create-trigger-from-template.ts +2 -2
- package/src/operations/run-routine.test.ts +150 -0
- package/src/operations/run-routine.ts +18 -2
- package/src/paths.ts +5 -5
- package/src/templates/index.ts +5 -3
- package/src/translations.ts +5 -6
- package/src/types/RoutineCapabilities.ts +10 -4
- package/src/types/RoutineOperation.ts +7 -6
- package/src/types/index.ts +0 -1
- package/src/util/index.ts +1 -0
- package/src/util/routines-for-object.test.ts +37 -5
- package/src/util/routines-for-object.ts +33 -8
- package/src/util/wire.test.ts +77 -0
- package/src/util/wire.ts +76 -0
- package/dist/lib/neutral/RoutineArticle-UNTMV4CC.mjs +0 -95
- package/dist/lib/neutral/RoutineArticle-UNTMV4CC.mjs.map +0 -7
- package/dist/lib/neutral/RoutineCompanion-ZZ6WMY7I.mjs +0 -249
- package/dist/lib/neutral/RoutineCompanion-ZZ6WMY7I.mjs.map +0 -7
- package/dist/lib/neutral/RoutinePlugin.mjs +0 -10
- package/dist/lib/neutral/RoutinePlugin.mjs.map +0 -7
- package/dist/lib/neutral/RoutinePlugin.node.mjs +0 -1133
- package/dist/lib/neutral/RoutinePlugin.node.mjs.map +0 -7
- package/dist/lib/neutral/RoutinePlugin.workerd.mjs +0 -36
- package/dist/lib/neutral/RoutinePlugin.workerd.mjs.map +0 -7
- package/dist/lib/neutral/RoutineSettings-ZJOM4F3S.mjs +0 -48
- package/dist/lib/neutral/RoutineSettings-ZJOM4F3S.mjs.map +0 -7
- package/dist/lib/neutral/RoutineTraceCompanion-5U6R7LJE.mjs +0 -236
- package/dist/lib/neutral/RoutineTraceCompanion-5U6R7LJE.mjs.map +0 -7
- package/dist/lib/neutral/SkillArticle-WWDNKW2Z.mjs +0 -27
- package/dist/lib/neutral/SkillArticle-WWDNKW2Z.mjs.map +0 -7
- package/dist/lib/neutral/app-graph-builder-FALWLZTI.mjs +0 -90
- package/dist/lib/neutral/app-graph-builder-FALWLZTI.mjs.map +0 -7
- package/dist/lib/neutral/capabilities/index.mjs +0 -25
- package/dist/lib/neutral/capabilities/index.mjs.map +0 -7
- package/dist/lib/neutral/capabilities/node.mjs +0 -19
- package/dist/lib/neutral/capabilities/node.mjs.map +0 -7
- package/dist/lib/neutral/chunk-674LDIUJ.mjs +0 -9
- package/dist/lib/neutral/chunk-674LDIUJ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-HLO4LYXT.mjs.map +0 -7
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-P3TDQKOO.mjs +0 -8
- package/dist/lib/neutral/chunk-P3TDQKOO.mjs.map +0 -7
- package/dist/lib/neutral/chunk-R7J2MU2M.mjs +0 -263
- package/dist/lib/neutral/chunk-R7J2MU2M.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WLNL77C7.mjs +0 -14
- package/dist/lib/neutral/chunk-WLNL77C7.mjs.map +0 -7
- package/dist/lib/neutral/chunk-YGWSAKLD.mjs +0 -65
- package/dist/lib/neutral/chunk-YGWSAKLD.mjs.map +0 -7
- package/dist/lib/neutral/components/index.mjs +0 -1921
- package/dist/lib/neutral/components/index.mjs.map +0 -7
- package/dist/lib/neutral/containers/index.mjs +0 -17
- package/dist/lib/neutral/containers/index.mjs.map +0 -7
- package/dist/lib/neutral/create-object-V23KVDMJ.mjs +0 -25
- package/dist/lib/neutral/create-object-V23KVDMJ.mjs.map +0 -7
- package/dist/lib/neutral/create-routine-6QBY6TWF.mjs +0 -38
- package/dist/lib/neutral/create-routine-6QBY6TWF.mjs.map +0 -7
- package/dist/lib/neutral/create-trigger-from-template-U3KNS77V.mjs +0 -70
- package/dist/lib/neutral/create-trigger-from-template-U3KNS77V.mjs.map +0 -7
- package/dist/lib/neutral/hooks/index.mjs +0 -47
- package/dist/lib/neutral/hooks/index.mjs.map +0 -7
- package/dist/lib/neutral/index.mjs +0 -27
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/layer-specs-2TWJU5HI.mjs +0 -135
- package/dist/lib/neutral/layer-specs-2TWJU5HI.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/dist/lib/neutral/meta.mjs +0 -8
- package/dist/lib/neutral/meta.mjs.map +0 -7
- package/dist/lib/neutral/navigation-resolver-KQIWUH6F.mjs +0 -18
- package/dist/lib/neutral/navigation-resolver-KQIWUH6F.mjs.map +0 -7
- package/dist/lib/neutral/operation-handler-QKLJSDCQ.mjs +0 -13
- package/dist/lib/neutral/operation-handler-QKLJSDCQ.mjs.map +0 -7
- package/dist/lib/neutral/operations/index.mjs +0 -8
- package/dist/lib/neutral/operations/index.mjs.map +0 -7
- package/dist/lib/neutral/plugin.mjs +0 -16
- package/dist/lib/neutral/plugin.mjs.map +0 -7
- package/dist/lib/neutral/react-surface-GMO7I7YE.mjs +0 -67
- package/dist/lib/neutral/react-surface-GMO7I7YE.mjs.map +0 -7
- package/dist/lib/neutral/registry-sync-DYPOC7ZG.mjs +0 -86
- package/dist/lib/neutral/registry-sync-DYPOC7ZG.mjs.map +0 -7
- package/dist/lib/neutral/run-routine-AEWO6SYD.mjs +0 -37
- package/dist/lib/neutral/run-routine-AEWO6SYD.mjs.map +0 -7
- package/dist/lib/neutral/templates-IIUCEBAX.mjs +0 -49
- package/dist/lib/neutral/templates-IIUCEBAX.mjs.map +0 -7
- package/dist/lib/neutral/testing.mjs +0 -8
- package/dist/lib/neutral/testing.mjs.map +0 -7
- package/dist/lib/neutral/translations.mjs +0 -115
- package/dist/lib/neutral/translations.mjs.map +0 -7
- package/dist/lib/neutral/trigger-runtime-controller-WIDDFGQT.mjs +0 -77
- package/dist/lib/neutral/trigger-runtime-controller-WIDDFGQT.mjs.map +0 -7
- package/dist/lib/neutral/types/index.mjs +0 -17
- package/dist/lib/neutral/types/index.mjs.map +0 -7
- package/dist/types/src/capabilities/navigation-resolver.d.ts +0 -5
- package/dist/types/src/capabilities/navigation-resolver.d.ts.map +0 -1
- package/dist/types/src/commands/trigger/create/queue.d.ts.map +0 -1
- package/dist/types/src/commands/trigger/update/queue.d.ts.map +0 -1
- package/dist/types/src/components/MasterDetail/MasterDetail.d.ts +0 -51
- package/dist/types/src/components/MasterDetail/MasterDetail.d.ts.map +0 -1
- package/dist/types/src/components/MasterDetail/MasterDetail.stories.d.ts +0 -16
- package/dist/types/src/components/MasterDetail/MasterDetail.stories.d.ts.map +0 -1
- package/dist/types/src/components/MasterDetail/index.d.ts +0 -2
- package/dist/types/src/components/MasterDetail/index.d.ts.map +0 -1
- package/dist/types/src/types/Routine.d.ts +0 -55
- package/dist/types/src/types/Routine.d.ts.map +0 -1
- package/dist/types/src/types/Routine.test.d.ts +0 -2
- package/dist/types/src/types/Routine.test.d.ts.map +0 -1
- package/src/capabilities/navigation-resolver.ts +0 -21
- package/src/components/MasterDetail/MasterDetail.stories.tsx +0 -100
- package/src/components/MasterDetail/MasterDetail.tsx +0 -175
- package/src/types/Routine.test.ts +0 -51
- package/src/types/Routine.ts +0 -144
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom, Registry } from '@effect-atom/atom';
|
|
6
|
+
import { describe, it } from '@effect/vitest';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import * as Layer from 'effect/Layer';
|
|
9
|
+
import * as Schema from 'effect/Schema';
|
|
10
|
+
|
|
11
|
+
import { AssistantTestLayer } from '@dxos/agent-runtime/testing';
|
|
12
|
+
import { Capability, CapabilityManager } from '@dxos/app-framework';
|
|
13
|
+
import { Operation, OperationHandlerSet, Routine, Trigger } from '@dxos/compute';
|
|
14
|
+
import { Database, DXN, Ref } from '@dxos/echo';
|
|
15
|
+
import { TestHelpers } from '@dxos/effect/testing';
|
|
16
|
+
|
|
17
|
+
import { RoutineOperation } from '../types';
|
|
18
|
+
import { makeRoutine } from '../util';
|
|
19
|
+
import RunRoutineHandler from './run-routine';
|
|
20
|
+
|
|
21
|
+
/** Captures the input each run receives so the test can assert on it after the invocation. */
|
|
22
|
+
const received: unknown[] = [];
|
|
23
|
+
|
|
24
|
+
/** Trigger ids the monitor was asked to invoke — the seam a `remote` trigger must reach instead of running here. */
|
|
25
|
+
const monitorInvocations: string[] = [];
|
|
26
|
+
|
|
27
|
+
/** Stands in for the aggregate monitor, whose real implementation routes `remote` triggers to EDGE over HTTP. */
|
|
28
|
+
const TestMonitor = Layer.succeed(Trigger.TriggerMonitorService, {
|
|
29
|
+
triggers: Atom.make<readonly Trigger.State[]>([]),
|
|
30
|
+
localDispatcherEnabled: false,
|
|
31
|
+
invokeTrigger: ({ trigger }) => Effect.sync(() => void monitorInvocations.push(trigger.id)),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Stand-in for a connector's sync operation: like `InboxOperation.GoogleMailSync` it destructures a
|
|
36
|
+
* required input, so a run that supplies none throws before the handler body.
|
|
37
|
+
*/
|
|
38
|
+
const TestRunnable = Operation.make({
|
|
39
|
+
meta: { key: DXN.make('org.dxos.test.runnable'), name: 'Test Runnable' },
|
|
40
|
+
input: Schema.Struct({ label: Schema.Any }),
|
|
41
|
+
output: Schema.Void,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const TestLayer = AssistantTestLayer({
|
|
45
|
+
operationHandlers: OperationHandlerSet.make(
|
|
46
|
+
RunRoutineHandler,
|
|
47
|
+
TestRunnable.pipe(
|
|
48
|
+
Operation.withHandler(({ label }) => {
|
|
49
|
+
received.push(label);
|
|
50
|
+
return Effect.void;
|
|
51
|
+
}),
|
|
52
|
+
),
|
|
53
|
+
),
|
|
54
|
+
types: [Routine.Routine, Trigger.Trigger, Operation.PersistentOperation],
|
|
55
|
+
disableLlmMemoization: true,
|
|
56
|
+
// `RunRoutine` declares `Capability.Service`; an empty manager discharges it (the handler never reads it).
|
|
57
|
+
extraServices: Layer.mergeAll(
|
|
58
|
+
Layer.succeed(Capability.Service, CapabilityManager.make({ registry: Registry.make() })),
|
|
59
|
+
TestMonitor,
|
|
60
|
+
),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('RunRoutine', () => {
|
|
64
|
+
it.effect(
|
|
65
|
+
'passes the trigger input to the runnable',
|
|
66
|
+
Effect.fnUntraced(
|
|
67
|
+
function* ({ expect }) {
|
|
68
|
+
received.length = 0;
|
|
69
|
+
const { routine } = yield* addRoutine({ label: 'from-trigger' });
|
|
70
|
+
|
|
71
|
+
yield* Operation.invoke(RoutineOperation.RunRoutine, { routine: Ref.make(routine) });
|
|
72
|
+
|
|
73
|
+
expect(received).toEqual(['from-trigger']);
|
|
74
|
+
},
|
|
75
|
+
Effect.provide(TestLayer),
|
|
76
|
+
TestHelpers.provideTestContext,
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
it.effect(
|
|
81
|
+
'resolves an event template against the synthetic run event',
|
|
82
|
+
Effect.fnUntraced(
|
|
83
|
+
function* ({ expect }) {
|
|
84
|
+
received.length = 0;
|
|
85
|
+
const { routine } = yield* addRoutine({ label: '{{event.tick}}' });
|
|
86
|
+
|
|
87
|
+
yield* Operation.invoke(RoutineOperation.RunRoutine, { routine: Ref.make(routine) });
|
|
88
|
+
|
|
89
|
+
expect(received).toHaveLength(1);
|
|
90
|
+
expect(received[0]).toBeTypeOf('number');
|
|
91
|
+
},
|
|
92
|
+
Effect.provide(TestLayer),
|
|
93
|
+
TestHelpers.provideTestContext,
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
it.effect(
|
|
98
|
+
'routes a remote trigger to the monitor instead of running the runnable locally',
|
|
99
|
+
Effect.fnUntraced(
|
|
100
|
+
function* ({ expect }) {
|
|
101
|
+
received.length = 0;
|
|
102
|
+
monitorInvocations.length = 0;
|
|
103
|
+
const { routine, trigger } = yield* addRoutine({ label: 'on-edge' }, true);
|
|
104
|
+
|
|
105
|
+
yield* Operation.invoke(RoutineOperation.RunRoutine, { routine: Ref.make(routine) });
|
|
106
|
+
|
|
107
|
+
// The monitor sends it to the EDGE dispatcher; nothing runs in this process.
|
|
108
|
+
expect(monitorInvocations).toEqual([trigger.id]);
|
|
109
|
+
expect(received).toEqual([]);
|
|
110
|
+
},
|
|
111
|
+
Effect.provide(TestLayer),
|
|
112
|
+
TestHelpers.provideTestContext,
|
|
113
|
+
),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
it.effect(
|
|
117
|
+
'runs a local trigger in-process without touching the monitor',
|
|
118
|
+
Effect.fnUntraced(
|
|
119
|
+
function* ({ expect }) {
|
|
120
|
+
received.length = 0;
|
|
121
|
+
monitorInvocations.length = 0;
|
|
122
|
+
const { routine } = yield* addRoutine({ label: 'on-client' });
|
|
123
|
+
|
|
124
|
+
yield* Operation.invoke(RoutineOperation.RunRoutine, { routine: Ref.make(routine) });
|
|
125
|
+
|
|
126
|
+
expect(received).toEqual(['on-client']);
|
|
127
|
+
expect(monitorInvocations).toEqual([]);
|
|
128
|
+
},
|
|
129
|
+
Effect.provide(TestLayer),
|
|
130
|
+
TestHelpers.provideTestContext,
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A routine whose action is `TestRunnable`, with the runnable's input carried on its timer trigger.
|
|
137
|
+
* Returns the trigger alongside so assertions can name its id without re-deriving it from the ref.
|
|
138
|
+
*/
|
|
139
|
+
const addRoutine = Effect.fnUntraced(function* (input: Record<string, unknown>, remote?: boolean) {
|
|
140
|
+
const operation = yield* Database.add(Operation.serialize(TestRunnable));
|
|
141
|
+
const trigger = Trigger.make({ enabled: true, remote, spec: Trigger.specTimer('*/10 * * * *'), input });
|
|
142
|
+
const routine = makeRoutine({
|
|
143
|
+
name: 'Test',
|
|
144
|
+
spec: { kind: 'runnable', runnable: Ref.make(operation) },
|
|
145
|
+
trigger,
|
|
146
|
+
});
|
|
147
|
+
yield* Database.add(routine);
|
|
148
|
+
yield* Database.flush();
|
|
149
|
+
return { routine, trigger };
|
|
150
|
+
});
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import * as Effect from 'effect/Effect';
|
|
6
6
|
|
|
7
7
|
import { RunInstructions } from '@dxos/assistant-toolkit';
|
|
8
|
-
import { Operation } from '@dxos/compute';
|
|
8
|
+
import { Operation, Trigger } from '@dxos/compute';
|
|
9
|
+
import { createInvocationPayload } from '@dxos/compute-runtime';
|
|
9
10
|
import { Database, Obj } from '@dxos/echo';
|
|
10
11
|
import { invariant } from '@dxos/invariant';
|
|
11
12
|
|
|
@@ -20,6 +21,18 @@ const handler: Operation.WithHandler<typeof RoutineOperation.RunRoutine> = Routi
|
|
|
20
21
|
const routineObj = yield* Database.load(routine);
|
|
21
22
|
const db = Obj.getDatabase(routineObj);
|
|
22
23
|
invariant(db, 'Routine is not attached to a database.');
|
|
24
|
+
// Triggers, not routines, carry the runnable's input binding (e.g. a sync routine's `binding`
|
|
25
|
+
// cursor) and the `remote` flag, so a manual run goes through the first trigger — the one it is
|
|
26
|
+
// also attributed to.
|
|
27
|
+
const trigger = routineObj.triggers[0] ? yield* Database.load(routineObj.triggers[0]) : undefined;
|
|
28
|
+
|
|
29
|
+
// An edge routine is registered only on the EDGE dispatcher, so running it means force-running that
|
|
30
|
+
// trigger over HTTP; invoking the runnable here would silently run it on the client instead.
|
|
31
|
+
if (trigger?.remote === true) {
|
|
32
|
+
const monitor = yield* Trigger.TriggerMonitorService;
|
|
33
|
+
yield* monitor.invokeTrigger({ trigger, event: { tick: Date.now() } });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
23
36
|
|
|
24
37
|
// The action kind is explicit in `spec`; dispatch by kind rather than dereferencing to classify.
|
|
25
38
|
const spec = routineObj.spec;
|
|
@@ -37,7 +50,10 @@ const handler: Operation.WithHandler<typeof RoutineOperation.RunRoutine> = Routi
|
|
|
37
50
|
invariant(spec?.kind === 'runnable', 'Routine has no action to run.');
|
|
38
51
|
const operation = yield* Database.load(spec.runnable);
|
|
39
52
|
invariant(Obj.instanceOf(Operation.PersistentOperation, operation), 'Routine has no action to run.');
|
|
40
|
-
|
|
53
|
+
// Built by the dispatcher's own payload builder against a synthetic tick, so a manual run resolves
|
|
54
|
+
// `{{event.*}}`/`{{trigger.*}}` templates exactly as a scheduled fire would.
|
|
55
|
+
const input = trigger ? createInvocationPayload(trigger, { tick: Date.now() }) : undefined;
|
|
56
|
+
yield* Operation.invoke(Operation.deserialize(operation), input);
|
|
41
57
|
}),
|
|
42
58
|
),
|
|
43
59
|
);
|
package/src/paths.ts
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
// Copyright 2026 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { GraphPath } from '@dxos/app-toolkit';
|
|
6
|
+
import { Routine } from '@dxos/compute';
|
|
6
7
|
|
|
7
8
|
import { meta } from '#meta';
|
|
8
|
-
import { Routine } from '#types';
|
|
9
9
|
|
|
10
|
-
const { getSectionPath: getRoutinesPath } =
|
|
11
|
-
groupId:
|
|
10
|
+
const { getSectionPath: getRoutinesPath } = GraphPath.createTypeSectionPaths(Routine.Routine, {
|
|
11
|
+
groupId: GraphPath.GroupSegments.ai,
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
/** Path to the routines settings section for a given space. */
|
|
15
15
|
export const getRoutinesSettingsPath = (spaceId: string): string =>
|
|
16
|
-
|
|
16
|
+
GraphPath.getSpacePath(spaceId, 'settings', `${meta.profile.key}.routines`);
|
|
17
17
|
|
|
18
18
|
export { getRoutinesPath };
|
package/src/templates/index.ts
CHANGED
|
@@ -9,7 +9,9 @@ import { AppAnnotation } from '@dxos/app-toolkit';
|
|
|
9
9
|
import { Instructions, Skill, Trigger } from '@dxos/compute';
|
|
10
10
|
import { Obj, Ref, Type } from '@dxos/echo';
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { RoutineCapabilities } from '#types';
|
|
13
|
+
|
|
14
|
+
import { makeRoutine } from '../util';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Blank template: an instructions-action routine draft with an empty trigger; the action kind, schedule, and
|
|
@@ -18,12 +20,12 @@ import { Routine, type RoutineCapabilities } from '#types';
|
|
|
18
20
|
* (no subject) starts with bare instructions.
|
|
19
21
|
*/
|
|
20
22
|
export const blank: RoutineCapabilities.Template = {
|
|
21
|
-
id:
|
|
23
|
+
id: RoutineCapabilities.BlankTemplateId,
|
|
22
24
|
label: 'Blank',
|
|
23
25
|
icon: 'ph--lightning--regular',
|
|
24
26
|
scaffold: ({ name, subject }) =>
|
|
25
27
|
Effect.succeed(
|
|
26
|
-
|
|
28
|
+
makeRoutine({
|
|
27
29
|
name,
|
|
28
30
|
instructions: Instructions.make({
|
|
29
31
|
skills: subject ? skillRefsForObject(subject) : [],
|
package/src/translations.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Routine } from '@dxos/compute';
|
|
5
6
|
import { Type } from '@dxos/echo';
|
|
6
7
|
import { type Resource } from '@dxos/react-ui';
|
|
7
8
|
import { translations as componentsTranslations } from '@dxos/react-ui-components/translations';
|
|
8
9
|
import { translations as formTranslations } from '@dxos/react-ui-form/translations';
|
|
9
10
|
|
|
10
11
|
import { meta } from '#meta';
|
|
11
|
-
import { Routine } from '#types';
|
|
12
12
|
|
|
13
13
|
export const translations: Resource[] = [
|
|
14
14
|
...componentsTranslations,
|
|
@@ -75,6 +75,8 @@ export const translations: Resource[] = [
|
|
|
75
75
|
'trigger-kind.feed.description': 'Run when a new item arrives in a feed.',
|
|
76
76
|
'trigger-kind.email.label': 'Email',
|
|
77
77
|
'trigger-kind.email.description': 'Run when an email is received.',
|
|
78
|
+
'trigger-kind.direct.label': 'Direct',
|
|
79
|
+
'trigger-kind.direct.description': 'Run only when invoked directly.',
|
|
78
80
|
'trigger-kind.clear.label': 'Clear',
|
|
79
81
|
'trigger-kind.query-stub.message': 'Query editing is not yet available.',
|
|
80
82
|
'trigger-kind.email-note.message': 'Runs whenever a new email is received.',
|
|
@@ -114,11 +116,8 @@ export const translations: Resource[] = [
|
|
|
114
116
|
'routine-verbose.label': 'Manage routines',
|
|
115
117
|
'routine.description': 'Manage where routines in this space run.',
|
|
116
118
|
|
|
117
|
-
'runtime.label': '
|
|
118
|
-
'runtime.description': '
|
|
119
|
-
'runtime.disabled.label': 'Disabled',
|
|
120
|
-
'runtime.local.label': 'Local',
|
|
121
|
-
'runtime.edge.label': 'EDGE (experimental)',
|
|
119
|
+
'runtime.label': 'Enable triggers',
|
|
120
|
+
'runtime.description': "Run this space's triggers. Individual triggers can be routed to the edge.",
|
|
122
121
|
},
|
|
123
122
|
},
|
|
124
123
|
},
|
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import type * as Effect from 'effect/Effect';
|
|
6
6
|
|
|
7
|
+
import type { DelegationStrategy } from '@dxos/agent-runtime';
|
|
7
8
|
import { Capability } from '@dxos/app-framework';
|
|
9
|
+
import type { Routine } from '@dxos/compute';
|
|
8
10
|
import type { Database, Obj } from '@dxos/echo';
|
|
9
|
-
import type { DelegationStrategy } from '@dxos/functions-runtime';
|
|
10
|
-
|
|
11
|
-
import * as Routine from './Routine';
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Optional supervisor strategy for the agent chat service. When contributed (by a plugin that knows
|
|
@@ -20,6 +19,13 @@ export const AgentDelegationStrategy = Capability.make<DelegationStrategy>(
|
|
|
20
19
|
'org.dxos.plugin.routine.capability.agentDelegationStrategy',
|
|
21
20
|
);
|
|
22
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Id of the built-in blank template. Declared here (rather than on the template itself) so callers that
|
|
24
|
+
* scaffold a routine without the picker — e.g. a project's toolbar — can name it without importing the
|
|
25
|
+
* template module.
|
|
26
|
+
*/
|
|
27
|
+
export const BlankTemplateId = 'org.dxos.routine.blank';
|
|
28
|
+
|
|
23
29
|
/**
|
|
24
30
|
* An automation template contributed by a plugin. The create dialog and the per-object "Automations"
|
|
25
31
|
* companion list contributed templates (`Capability.getAll(RoutineCapabilities.Template)`) and run the
|
|
@@ -40,7 +46,7 @@ export type Template = {
|
|
|
40
46
|
appliesTo?: (subject?: Obj.Unknown) => boolean;
|
|
41
47
|
/**
|
|
42
48
|
* Build the routine as a fully-wired in-memory {@link Routine.Routine} graph — the routine plus its owned
|
|
43
|
-
* trigger and instructions, assembled by `
|
|
49
|
+
* trigger and instructions, assembled by `makeRoutine`. The create flow persists it with a single
|
|
44
50
|
* `Database.add` (which cascades the owned children); scaffold must NOT call `Database.add` itself.
|
|
45
51
|
* `Database.Service` may still be used for read-only lookups (e.g. loading a feed ref). `subject` is set
|
|
46
52
|
* when scaffolding from an object's companion.
|
|
@@ -8,7 +8,7 @@ import * as Schema from 'effect/Schema';
|
|
|
8
8
|
|
|
9
9
|
import { Capability } from '@dxos/app-framework';
|
|
10
10
|
import { Chat } from '@dxos/assistant-toolkit';
|
|
11
|
-
import { Instructions, Operation } from '@dxos/compute';
|
|
11
|
+
import { Instructions, Operation, Routine, Trigger } from '@dxos/compute';
|
|
12
12
|
import { Database, DXN, Obj, Ref, Type } from '@dxos/echo';
|
|
13
13
|
// Value-side `EID` import keeps TS declaration emit portable — `TriggerTemplate`
|
|
14
14
|
// references `EID.Schema` and the inferred `CreateTriggerFromTemplate` type
|
|
@@ -17,7 +17,6 @@ import { EID as _EchoURIReference } from '@dxos/keys';
|
|
|
17
17
|
|
|
18
18
|
import { meta } from '#meta';
|
|
19
19
|
|
|
20
|
-
import * as Routine from './Routine';
|
|
21
20
|
import { TriggerTemplate } from './schema';
|
|
22
21
|
export { _EchoURIReference };
|
|
23
22
|
|
|
@@ -87,16 +86,18 @@ export const RunPromptInNewChat = Operation.make({
|
|
|
87
86
|
}),
|
|
88
87
|
});
|
|
89
88
|
|
|
90
|
-
// Runs a routine's
|
|
91
|
-
//
|
|
92
|
-
//
|
|
89
|
+
// Runs a routine's action now, routed by its first trigger — that is where both the runnable's input
|
|
90
|
+
// (e.g. a sync routine's binding cursor) and the `remote` flag live. A `remote` trigger force-runs on the
|
|
91
|
+
// EDGE dispatcher over HTTP, since that is the only runtime it is registered on; a local one runs
|
|
92
|
+
// in-process. Without the trigger a manual run would invoke an input-taking runnable with nothing, and
|
|
93
|
+
// would silently run an edge routine on the client.
|
|
93
94
|
export const RunRoutine = Operation.make({
|
|
94
95
|
meta: {
|
|
95
96
|
key: makeKey('runAutomation'),
|
|
96
97
|
name: 'Run Routine',
|
|
97
98
|
icon: 'ph--play--regular',
|
|
98
99
|
},
|
|
99
|
-
services: [Capability.Service],
|
|
100
|
+
services: [Capability.Service, Trigger.TriggerMonitorService],
|
|
100
101
|
input: Schema.Struct({
|
|
101
102
|
routine: Ref.Ref(Routine.Routine),
|
|
102
103
|
}),
|
package/src/types/index.ts
CHANGED
package/src/util/index.ts
CHANGED
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
import * as Schema from 'effect/Schema';
|
|
6
6
|
import { describe, test } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import { Instructions, Trigger } from '@dxos/compute';
|
|
8
|
+
import { Instructions, Routine, Trigger } from '@dxos/compute';
|
|
9
9
|
import { type Database, DXN, Feed, Obj, Ref, Type } from '@dxos/echo';
|
|
10
10
|
import { EffectEx } from '@dxos/effect';
|
|
11
11
|
import { EID, URI } from '@dxos/keys';
|
|
12
|
+
import { AccessToken, Cursor } from '@dxos/link';
|
|
12
13
|
import { ClientCapabilities, ClientEvents } from '@dxos/plugin-client';
|
|
13
14
|
import { ClientPlugin, initializeIdentity } from '@dxos/plugin-client/testing';
|
|
14
15
|
import { createComposerTestApp } from '@dxos/plugin-testing/harness';
|
|
15
16
|
|
|
16
|
-
import { Routine } from '#types';
|
|
17
|
-
|
|
18
17
|
import { connectedRoutinesQuery, routinesForObject } from './routines-for-object';
|
|
18
|
+
import { makeRoutine } from './wire';
|
|
19
19
|
|
|
20
20
|
// A minimal feed-annotated host (like a mailbox): an object holding a `feed` ref.
|
|
21
21
|
const FeedHost = Type.makeObject(DXN.make('org.dxos.test.feedHost', '0.1.0'))(
|
|
@@ -25,7 +25,15 @@ const FeedHost = Type.makeObject(DXN.make('org.dxos.test.feedHost', '0.1.0'))(
|
|
|
25
25
|
}),
|
|
26
26
|
);
|
|
27
27
|
|
|
28
|
-
const types = [
|
|
28
|
+
const types = [
|
|
29
|
+
Routine.Routine,
|
|
30
|
+
Trigger.Trigger,
|
|
31
|
+
Instructions.Instructions,
|
|
32
|
+
Feed.Feed,
|
|
33
|
+
FeedHost,
|
|
34
|
+
Cursor.Cursor,
|
|
35
|
+
AccessToken.AccessToken,
|
|
36
|
+
];
|
|
29
37
|
|
|
30
38
|
const initSpace = async (harness: Awaited<ReturnType<typeof createComposerTestApp>>) => {
|
|
31
39
|
const { personalSpace } = await EffectEx.runAndForwardErrors(
|
|
@@ -68,6 +76,30 @@ describe('routines connected to an object', () => {
|
|
|
68
76
|
await expect.poll(() => connectedIds(db, other), { timeout: 5_000 }).toEqual([]);
|
|
69
77
|
});
|
|
70
78
|
|
|
79
|
+
test('cursor-ref: trigger bound (via `binding`) to a Cursor whose `spec.target` is the object', async ({
|
|
80
|
+
expect,
|
|
81
|
+
}) => {
|
|
82
|
+
await using harness = await createComposerTestApp({ plugins: [ClientPlugin({ types })] });
|
|
83
|
+
const db = await initSpace(harness);
|
|
84
|
+
|
|
85
|
+
const target = db.add(Routine.make({ name: 'target', triggers: [] }));
|
|
86
|
+
const other = db.add(Routine.make({ name: 'other', triggers: [] }));
|
|
87
|
+
// A sync trigger references its target only indirectly: `input.binding` → Cursor → `spec.target` → target.
|
|
88
|
+
const token = db.add(AccessToken.make({ source: 'github.com', token: 'tok' }));
|
|
89
|
+
const cursor = db.add(Cursor.makeExternal({ source: Ref.make(token), target: qualifiedRef(db, target) }));
|
|
90
|
+
const trigger = db.add(
|
|
91
|
+
Trigger.make({ spec: Trigger.specTimer('*/10 * * * *'), input: { binding: qualifiedRef(db, cursor) } }),
|
|
92
|
+
);
|
|
93
|
+
const owner = db.add(Routine.make({ name: 'owner', triggers: [Ref.make(trigger)] }));
|
|
94
|
+
await db.flush();
|
|
95
|
+
|
|
96
|
+
expect(routinesForObject(target, [owner, other]).map((routine) => routine.id)).toEqual([owner.id]);
|
|
97
|
+
expect(routinesForObject(other, [owner])).toEqual([]);
|
|
98
|
+
|
|
99
|
+
await expect.poll(() => connectedIds(db, target), { timeout: 5_000 }).toEqual([owner.id]);
|
|
100
|
+
await expect.poll(() => connectedIds(db, other), { timeout: 5_000 }).toEqual([]);
|
|
101
|
+
});
|
|
102
|
+
|
|
71
103
|
test("feed-ref: feed trigger bound to the object's feed", async ({ expect }) => {
|
|
72
104
|
await using harness = await createComposerTestApp({ plugins: [ClientPlugin({ types })] });
|
|
73
105
|
const db = await initSpace(harness);
|
|
@@ -97,7 +129,7 @@ describe('routines connected to an object', () => {
|
|
|
97
129
|
// The routine's action is owned instructions that bind the object as a context object — the second
|
|
98
130
|
// connection path (O ← Instructions.objects ← Routine via spec.instructions), with no trigger involved.
|
|
99
131
|
const instructions = db.add(Instructions.make({ objects: [qualifiedRef(db, target)] }));
|
|
100
|
-
const owner = db.add(
|
|
132
|
+
const owner = db.add(makeRoutine({ name: 'owner', instructions }));
|
|
101
133
|
await db.flush();
|
|
102
134
|
|
|
103
135
|
expect(routinesForObject(target, [owner, other]).map((routine) => routine.id)).toEqual([owner.id]);
|
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
// Copyright 2026 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Instructions, Trigger } from '@dxos/compute';
|
|
5
|
+
import { Instructions, Routine, Trigger } from '@dxos/compute';
|
|
6
6
|
import { Filter, Obj, Query, Ref } from '@dxos/echo';
|
|
7
|
-
|
|
8
|
-
import { Routine } from '#types';
|
|
7
|
+
import { Cursor } from '@dxos/link';
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
|
-
* Reactive query for all routines connected to an object O, via
|
|
10
|
+
* Reactive query for all routines connected to an object O, via three structural paths:
|
|
12
11
|
*
|
|
13
12
|
* 1. **Trigger path** (two hops): O is referenced by a Trigger (via `input` or `spec.feed`), and that
|
|
14
13
|
* Trigger is referenced by a Routine's `triggers` array.
|
|
15
|
-
* 2. **
|
|
14
|
+
* 2. **Cursor path** (three hops): a sync Trigger doesn't reference its synced target directly — its `binding`
|
|
15
|
+
* references an external-sync {@link Cursor}, and the Cursor's `spec.target` references O. So O is reached
|
|
16
|
+
* one hop further out (O ← Cursor ← Trigger ← Routine), keeping the target ref out of the operation input.
|
|
17
|
+
* 3. **Instructions path** (two hops): O is listed in an Instructions' `objects` context array, and those
|
|
16
18
|
* Instructions are the Routine's action (`spec.instructions`). Both hops traverse `Ref` fields, so they are
|
|
17
19
|
* fully queryable — no JavaScript parent-symbol traversal needed.
|
|
18
20
|
*
|
|
@@ -25,7 +27,9 @@ export const connectedRoutinesQuery = (object: Obj.Unknown): Query.Query<Routine
|
|
|
25
27
|
// Feed variant: O's feed ← Trigger.spec.feed ← Routine.triggers.
|
|
26
28
|
// `.reference('feed')` is empty for objects without a feed prop, so this adds nothing for non-feed objects.
|
|
27
29
|
const byFeed = Query.select(Filter.id(object.id)).reference('feed').referencedBy(Trigger.Trigger);
|
|
28
|
-
|
|
30
|
+
// Cursor variant: O ← Cursor (spec.target) ← Trigger (input.binding) ← Routine.triggers.
|
|
31
|
+
const byCursor = Query.select(Filter.id(object.id)).referencedBy(Cursor.Cursor).referencedBy(Trigger.Trigger);
|
|
32
|
+
const byTrigger = Query.all(byInput, byFeed, byCursor).referencedBy(Routine.Routine, 'triggers');
|
|
29
33
|
|
|
30
34
|
// Instructions path: O ← Instructions.objects ← Routine (via `spec.instructions`). The second hop drops the
|
|
31
35
|
// property key: the instructions ref is nested in the `spec` union, and the reverse-ref index is structural,
|
|
@@ -47,7 +51,10 @@ export const routinesForObject = (object: Obj.Unknown, routines: Routine.Routine
|
|
|
47
51
|
export const routineReferencesObject = (routine: Routine.Routine, object: Obj.Unknown): boolean =>
|
|
48
52
|
referencesViaTrigger(routine, object) || referencesViaInstructions(routine, object);
|
|
49
53
|
|
|
50
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Trigger path: a trigger's `input` references O (directly, or via a bound {@link Cursor} whose `spec.target`
|
|
56
|
+
* is O), or a feed trigger is bound to O's feed.
|
|
57
|
+
*/
|
|
51
58
|
const referencesViaTrigger = (routine: Routine.Routine, object: Obj.Unknown): boolean => {
|
|
52
59
|
const objectFeedId = getFeedId(object);
|
|
53
60
|
return routine.triggers.some((ref) => {
|
|
@@ -60,7 +67,10 @@ const referencesViaTrigger = (routine: Routine.Routine, object: Obj.Unknown): bo
|
|
|
60
67
|
return true;
|
|
61
68
|
}
|
|
62
69
|
|
|
63
|
-
|
|
70
|
+
if (trigger.input == null) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return referencesId(trigger.input, object.id) || referencesViaCursor(trigger.input, object.id);
|
|
64
74
|
});
|
|
65
75
|
};
|
|
66
76
|
|
|
@@ -74,6 +84,21 @@ const getFeedId = (object: Obj.Unknown): string | undefined => {
|
|
|
74
84
|
return Ref.isRef(feedRef) ? feedRef.target?.id : undefined;
|
|
75
85
|
};
|
|
76
86
|
|
|
87
|
+
/** Recursively scan a value for a Cursor ref (e.g. a sync trigger's `binding`) that itself references O. */
|
|
88
|
+
const referencesViaCursor = (value: unknown, id: string): boolean => {
|
|
89
|
+
if (Ref.isRef(value)) {
|
|
90
|
+
const cursor = value.target;
|
|
91
|
+
return Obj.instanceOf(Cursor.Cursor, cursor) && referencesId(cursor.spec, id);
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(value)) {
|
|
94
|
+
return value.some((entry) => referencesViaCursor(entry, id));
|
|
95
|
+
}
|
|
96
|
+
if (value && typeof value === 'object') {
|
|
97
|
+
return Object.values(value).some((entry) => referencesViaCursor(entry, id));
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
};
|
|
101
|
+
|
|
77
102
|
/** Recursively scan a value for a Ref whose target is the given object id. */
|
|
78
103
|
const referencesId = (value: unknown, id: string): boolean => {
|
|
79
104
|
if (Ref.isRef(value)) {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { Instructions, Operation, Routine, Runnable, Trigger } from '@dxos/compute';
|
|
8
|
+
import { Obj, Ref } from '@dxos/echo';
|
|
9
|
+
|
|
10
|
+
import { blank } from '../templates';
|
|
11
|
+
import { isRunInstructions, runInstructionsRef } from './run-instructions';
|
|
12
|
+
import { makeRoutine } from './wire';
|
|
13
|
+
|
|
14
|
+
describe('wire', () => {
|
|
15
|
+
test('makeRoutine produces a typed Routine', ({ expect }) => {
|
|
16
|
+
const routine = makeRoutine({ name: 'Test', triggers: [] });
|
|
17
|
+
expect(Routine.instanceOf(routine)).toBe(true);
|
|
18
|
+
expect(Obj.instanceOf(Routine.Routine, routine)).toBe(true);
|
|
19
|
+
expect(Obj.getLabel(routine)).toBe('Test');
|
|
20
|
+
expect(routine.triggers).toEqual([]);
|
|
21
|
+
expect(routine.spec).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('Runnable seam is currently the Operation type', ({ expect }) => {
|
|
25
|
+
// MVP: Runnable === Operation. Widening this to a union is the documented next step.
|
|
26
|
+
expect(Runnable.Runnable).toBe(Operation.PersistentOperation);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('makeRoutine wires an instructions action so a single add yields a runnable routine', ({ expect }) => {
|
|
30
|
+
const instructions = Instructions.make({ name: 'Body', text: 'do something' });
|
|
31
|
+
const trigger = Trigger.make({ spec: Trigger.specTimer('0 9 * * *') });
|
|
32
|
+
const routine = makeRoutine({ name: 'R', instructions, trigger });
|
|
33
|
+
|
|
34
|
+
// The routine owns the instructions, and the trigger dispatches RunInstructions with those instructions
|
|
35
|
+
// bound into the trigger input so no separate persistence step is needed.
|
|
36
|
+
expect(Routine.instructionsRef(routine)?.target?.id).toBe(instructions.id);
|
|
37
|
+
expect(routine.triggers[0]?.target?.id).toBe(trigger.id);
|
|
38
|
+
expect(isRunInstructions(trigger.runnable)).toBe(true);
|
|
39
|
+
const bound = trigger.input?.instructions;
|
|
40
|
+
expect(Ref.isRef(bound) ? bound.target?.id : undefined).toBe(instructions.id);
|
|
41
|
+
// A trigger with no explicit `remote` flag runs locally.
|
|
42
|
+
expect(trigger.remote).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('makeRoutine preserves an explicit trigger remote override', ({ expect }) => {
|
|
46
|
+
const trigger = Trigger.make({ remote: true });
|
|
47
|
+
const routine = makeRoutine({ name: 'R', trigger });
|
|
48
|
+
expect(routine.triggers[0]?.target?.remote).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('makeRoutine wires triggers supplied via the `triggers` array', ({ expect }) => {
|
|
52
|
+
const instructions = Instructions.make({ name: 'Body', text: 'do something' });
|
|
53
|
+
const trigger = Trigger.make({ spec: Trigger.specTimer('0 9 * * *') });
|
|
54
|
+
const routine = makeRoutine({ name: 'R', instructions, triggers: [Ref.make(trigger)] });
|
|
55
|
+
expect(isRunInstructions(trigger.runnable)).toBe(true);
|
|
56
|
+
const bound = trigger.input?.instructions;
|
|
57
|
+
expect(Ref.isRef(bound) ? bound.target?.id : undefined).toBe(instructions.id);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('makeRoutine leaves pre-set trigger bindings alone when there is no action', ({ expect }) => {
|
|
61
|
+
const trigger = Trigger.make({});
|
|
62
|
+
const preset = runInstructionsRef();
|
|
63
|
+
Obj.update(trigger, (trigger) => {
|
|
64
|
+
trigger.runnable = preset;
|
|
65
|
+
});
|
|
66
|
+
makeRoutine({ name: 'R', trigger });
|
|
67
|
+
expect(trigger.runnable?.uri).toBe(preset.uri);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('blank template', () => {
|
|
71
|
+
test('is the default no-op template', ({ expect }) => {
|
|
72
|
+
expect(blank.id).toBe('org.dxos.routine.blank');
|
|
73
|
+
expect(blank.label).toBe('Blank');
|
|
74
|
+
expect(typeof blank.scaffold).toBe('function');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|