pkg: '@nocobase/plugin-workflow-javascript'

JavaScript スクリプト

紹介

JavaScript スクリプトノードを使用すると、ユーザーはワークフロー内でカスタムのサーバーサイド JavaScript スクリプトを実行できます。スクリプトでは、ワークフローの上流の変数をパラメーターとして使用でき、スクリプトの戻り値を下流のノードに提供して使用できます。

スクリプトはNocoBaseアプリケーションのサーバーサイドでワーカースレッドとして実行されます。デフォルトでは、requireやNode.js組み込みAPIをサポートしないセキュアなサンドボックス(WebAssembly ベースの QuickJS)を使用します。詳細は実行エンジンおよび機能一覧をご覧ください。

ノードの作成

ワークフロー設定インターフェースで、フロー内のプラス(「+」)ボタンをクリックし、「JavaScript」ノードを追加します。

20241202203457

ノードの設定

20241202203655

パラメーター

スクリプト内のコードロジックで使用するために、ワークフローコンテキストの変数や静的な値をスクリプトに渡すために使用します。そのうち name はパラメーター名で、スクリプトに渡された後は変数名として使用されます。value はパラメーター値で、変数を選択するか定数を入力できます。

スクリプト内容

スクリプト内容は一つの関数と見なすことができ、Node.js 環境でサポートされている任意の JavaScript コードを記述できます。また、return 文を使用してノードの実行結果として値を返し、後続のノードで変数として使用できます。

コードを記述した後、エディタの下にあるテストボタンをクリックしてテスト実行のダイアログを開き、静的な値をパラメーターに入力してシミュレーション実行を行うことができます。実行後、ダイアログで戻り値と出力(ログ)の内容を確認できます。

20241202203833

タイムアウト設定

単位はミリ秒で、0 に設定するとタイムアウトを設定しないことを意味します。

エラー発生後にフローを継続

チェックを入れると、スクリプトのエラーやタイムアウトエラーが発生しても、後続のノードが実行されます。

ヒント

スクリプトがエラーになった後は戻り値がなく、ノードの結果はエラーメッセージで埋められます。後続のノードでスクリプトノードの結果変数を使用する場合は、慎重に処理する必要があります。

実行エンジン

JavaScriptスクリプトノードは、環境変数WORKFLOW_SCRIPT_MODULESの設定有無に基づいて自動的に選択される2つの実行エンジンをサポートしています。

セーフモード(デフォルト)

WORKFLOW_SCRIPT_MODULES未設定の場合、スクリプトはQuickJS(WebAssembly ベース)エンジンを使用して実行されます。このエンジンは、隔離された JavaScript ランタイムでコードを実行し、以下の特性があります。

  • requireサポートしません — モジュールのインポートはできません
  • Node.js組み込みAPI(processBufferglobalなど)をサポートしません
  • ECMAScript標準の組み込みオブジェクト(JSONMathPromiseDateなど)のみ使用可能
  • パラメーターによるデータの受け渡し、consoleによるログ出力、async/awaitをサポート

これは推奨されるデフォルトモードであり、純粋な計算やデータ処理ロジックに適しており、最高レベルのセキュリティ分離を提供します。

非セーフモード(モジュールサポート)

WORKFLOW_SCRIPT_MODULES設定されている場合、require機能を有効にするため、スクリプトはNode.js組み込みのvmエンジンに切り替わります。

セキュリティ警告

非セーフモードでは、CommonJS の require を提供するためだけに Node.js の vm を使用します。Node.js の vm モジュールはセキュアなサンドボックスメカニズムではありません。このモードを有効にすることは、ワークフロースクリプトを編集、テスト、または実行できるすべてのユーザーを、NocoBase サーバーの権限でコードを実行できるユーザーとして信頼することを意味します。

WORKFLOW_SCRIPT_MODULES はセキュリティ境界でも権限モデルでもありません。スクリプトコードの実行前に、require() が受け付けるモジュール名を制御するだけです。

スクリプトでは、CommonJSと同様に、require()ディレクティブを使用してモジュールをインポートできます。

Node.js ネイティブモジュール、および node_modules にインストールされているモジュール(NocoBase が既に使用している依存パッケージを含む)をサポートしています。コードで使用できるようにするモジュールは、アプリケーションの環境変数 WORKFLOW_SCRIPT_MODULES で宣言する必要があり、複数のパッケージ名は半角カンマで区切ります。例:

WORKFLOW_SCRIPT_MODULES=crypto,timers,lodash,dayjs
ヒント

環境変数WORKFLOW_SCRIPT_MODULESで宣言されていないモジュールは、Node.jsネイティブのものであっても、node_modulesにインストール済みであっても、require() で直接インポートすることはできません。このリストは、サポートするインポートを設定するためだけのものです。スクリプト権限を下げたり、信頼度の低いユーザーへスクリプト編集権限を安全に委任したりする目的で依存しないでください。

ソースコードデプロイではない環境で、特定のモジュールが node_modules にインストールされていない場合、必要なパッケージを storage ディレクトリに手動でインストールできます。例えば、exceljs パッケージを使用する必要がある場合は、以下の操作を実行します。

cd storage
npm i --no-save --no-package-lock --prefix . exceljs

その後、アプリケーションの CWD(現在の作業ディレクトリ)を基準とした相対パス(または絶対パス)で、そのパッケージを環境変数 WORKFLOW_SCRIPT_MODULES に追加します。

WORKFLOW_SCRIPT_MODULES=./storage/node_modules/exceljs

これで、スクリプト内で exceljs パッケージを使用できます(require で使用する名前は、環境変数で定義された名前と正確に一致する必要があります)。

const ExcelJS = require('./storage/node_modules/exceljs');
// ...

機能一覧

Node.js バージョン

メインアプリケーションが実行しているNode.jsのバージョンと同じです。

グローバル変数

globalprocess__dirname__filename などのグローバル変数はサポートされていません

console.log(global); // will throw error: "global is not defined"

パラメーターの入力

ノードで設定されたパラメーターはスクリプト内のグローバル変数として扱われ、直接使用できます。スクリプトに渡されるパラメーターは、booleannumberstringobject、配列などの基本型のみをサポートしています。Date オブジェクトは渡された後、ISO 形式の文字列に変換されます。カスタムクラスのインスタンスなど、その他の複雑な型は直接渡すことができません。

戻り値

return 文を使用すると、基本型のデータ(パラメーターのルールと同様)をノードの結果として返すことができます。コード内で return 文が呼び出されない場合、ノードの実行に戻り値はありません。

return 123;

出力(ログ)

console を使用したログ出力をサポートしています

console.log('hello world!');

ワークフローの実行時、スクリプトノードの出力も対応するワークフローのログファイルに記録されます。

非同期

async を使用した非同期関数の定義、および await を使用した非同期関数の呼び出しをサポートしていますPromise グローバルオブジェクトの使用をサポートしています

async function test() {
  return Promise.resolve(1);
}

const value = await test();
return value;

タイマー

setTimeoutsetIntervalsetImmediate などのメソッドを使用する場合は、Node.js の timers パッケージを介してインポートする必要があります(非セーフモードでのみ利用可能)。

const { setTimeout, setInterval, setImmediate, clearTimeout, clearInterval, clearImmediate } = require('timers');

async function sleep(time) {
  return new Promise((resolve) => setTimeout(resolve, time));
}

await sleep(1000);

return 123;