nocobase-dsl-reconciler について
nocobase-dsl-reconciler は、コミット可能な YAML DSL で NocoBase アプリを構築し、CLI でデプロイするオプトイン型スキルです。スターターからの段階的な構築やデプロイ前の検証を案内し、DSL の変更とライブ UI の編集を区別します。
主な機能
- 01スターターテンプレートからの構築
- 02YAML によるコレクション、ルート、ページ、ポップアップの定義
- 03CLI による NocoBase への変更反映
- 04グループやページを指定した段階的なデプロイ
- 05デプロイ前の DSL 仕様検証
活用シーン
- 01コミット可能な YAML による CRUD モジュールの構築
- 02コレクションやページの段階的な追加
- 03YAML 作成前の複数コレクション構成の設計
- 04指定ページへの DSL 変更のデプロイ
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ nocobase/ skills --skill nocobase-dsl-reconcilerインストール後、Claude Code で/nocobase-dsl-reconcilerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
exports/crm-opportunities/structure.yamlexports/crm-opportunities/js/opportunities_filterForm_0_pipeline_stats_filter_block.jsexports/crm-v2/enhance.yamlexports/crm-v2/charts/analytics_chart_1.jsonanalytics_chart_2.jsonanalytics_chart_3.jsonanalytics_chart_4.jsonanalytics_chart_5.jsonexports/crm-v2/js/analytics_filterForm_0_js_0.jsanalytics_jsBlock_6.jsanalytics_jsBlock_7.jsanalytics_jsBlock_8.jsanalytics_jsBlock_9.jscustomers_filterForm_1_customer_stats_filter_block.jscustomers_popup_name_tab0_contacts_contact_row_column_customer_360.jscustomers_popup_name_tab0_details_0_customer_statistics_block.jscustomers_popup_name_tab0_details_0_item_profile_customer_profile_header.jscustomers_popup_name_tab0_details_1_ai_smart_profile_block.jscustomers_popup_name_tab0_opportunities_opportunity_row_column_customer_360.jscustomers_table_0_col_health_score.jscustomers_table_0_col_last_activity.jsleads_filterForm_1_leads_stats_filter_block.jsleads_popup_name_tab0_details_0_lead_badges_row_component.jsleads_popup_name_tab0_details_0_lead_key_stats_block.jsleads_popup_name_tab0_details_5_lead_stage_flow_block.jsleads_popup_name_tab0_lead_insights_lead_ai_insights_block.jsleads_popup_name_tab0_lead_owner_lead_profile_header_block.jsleads_table_0_col_ai_score.jsleads_table_0_col_contact.jsleads_table_0_col_created.js詳細
NocoBase アプリケーションビルダー(DSL パス)
このスキルを使用する前に
これはオプトインの DSL パスです。デフォルトは nocobase-ui-builder です。
ユーザーが、コミット可能な YAML ファイルと cli push を明示的に希望している場合に限り、このスキルを使用してください。ユーザーが DSL/YAML/git を指定せずに、一般的な「NocoBase アプリを作って」という依頼からここにたどり着いた場合は、デフォルトのエントリーポイントである nocobase-ui-builder に切り替えてください。
最も重要なルール
templates/crm/ は読み取り専用の参照ライブラリです。特定のフィールド、ブロック、ポップアップの形が分からない場合は、最も近い CRM の例を開いて読み、自分のワークスペースで調整したものを作成してください。シナリオごとの参照先は、以下のワークフローのセクションにあります。
CRM のファイルを丸ごとワークスペースにコピーしてはいけません。作業を始めるために cp -r templates/crm/... を実行したり、collections/nb_crm_*.yaml を複製したり、CRM の routes.yaml をベースにしたりしないでください。大量にコピーすると、関係のないリード/商談/注文の状態やワークフローまでプロジェクトに持ち込まれ、モジュールを作る代わりに、何百もの無関係なバリデーターエラーへの対処にセッションを費やすことになります。
デプロイ前の仕様バリデーターは、構造上のミスの大半を明確なエラーメッセージで検出します。バリデーターを信頼してください。エラーが出たら、推測せずに指摘された内容を修正してください。src/deploy/*.ts を grep しないでください。
このスキルを使用しない場合
ユーザーの依頼がこのスキルの対象外である場合は、該当するスキルに引き継いでください。
| ユーザーの依頼… | スキル |
|---|---|
| すでに稼働しているページの UI を一度だけ変更(単一のブロック、フィールド、アクションの移動/並べ替え/再設定)— DSL のコミットは不要 | nocobase-ui-builder |
| ACL/ロール権限/ルート権限 | nocobase-acl-manage |
| ワークフローの作成/更新/改訂/実行 | nocobase-workflow-manage(このスキルではトリガーボタンのみを配線します。グラフの作成はそちらで行います) |
| DSL プロジェクト外でのコレクション/フィールド/リレーションの作成 | nocobase-data-modeling |
プラグイン開発(.tsx コンポーネント、サーバーコード) | nocobase-plugin-development |
| プラグインのインストール/有効化 | nocobase-plugin-manage |
| 環境のセットアップ/アプリのインストール/アップグレード | nocobase-env-manage |
workspaces/<project>/ 配下のコミット対象 YAML ファイルとして保存する変更は、すべてこのスキルで扱います。
環境
cd <skill-dir>/src
export [email protected] NB_PASSWORD=admin123 NB_URL=http://localhost:14000
クイックスタート — 新規構築
「NocoBase アプリを作って」という依頼のデフォルトパス:スターターをコピーして変更します。スケルトンを一から手書きしたり、最初に CRM を調べたりしないでください。
cd <skill-dir>/src
cp -r ../templates/starter ../workspaces/<name>
# First push: --copy bypasses validation rules that only matter once
# popups exist (m2o popup binding, clickToOpen file presence). The
# starter ships with its own popups so the first push is actually fully
# valid — --copy is for your *extensions* before popups are wired.
npx tsx cli/cli.ts push <name> --force
スターターは、最小限の CRUD が一通りそろった構成です — 1 つのコレクション(Projects)、1 つのダッシュボードページ(4 つの KPI タイル + 2 つのチャート)、filterForm/table/addNew ポップアップ/detail ポップアップ/2 つの updateRecord recordActions を備えた 1 つの一覧ページです。そのままプッシュ → NB 上で表示を確認 → その後に編集します。
スターターのカスタマイズ(アジャイルな反復)
一度に 1 つの項目だけを変更し、その都度プッシュします。
- 識別子の名前を変更して、ユーザーのドメインに合わせます。
collections/nb_starter_projects.yaml→ 使用するコレクション名 (nb_<module>_<entity>の規則に合わせます)routes.yaml:「Starter」/「Projects」のタイトルを変更します- 必要であれば
pages/starter/ディレクトリ名を変更します(routes.yamlのキーに合わせます) - すべてのファイルで
nb_starter_projectsを検索して置換します
- コレクション YAML のフィールドリストを、ユーザーのエンティティに合わせて調整します。
pages/<...>/layout.yamlのfields:と、ポップアップテンプレートのfield_layout:もそれに応じて更新します。 - 1 つ目のエンティティが最初から最後まで動作してから、2 つ目のエンティティを追加します。新しい
collections/*.yamlとpages/<module>/<entity>/ディレクトリを作成し、pages/starter/projects/layout.yamlを出発点としてコピーします。 - 段階的に拡張します:タブ、チャート、ワークフロートリガーを追加します。変更するたびにプッシュしてください。どのパターンにどの CRM ファイルが対応するかは、以下の「高度なパターン」を参照してください。
モジュール全体を一度に作成しないでください。 ユーザー向けの構築では、まずスケルトンを作成してユーザーに見せ、フィードバックを集めて反復します。スターターのプッシュは数分で済みますが、手作業で作るモジュールには何時間もかかります。
短時間で進める:--copy が役立つ場合
ワークスペースにまだポップアップファイルがない場合は、--copy を指定します(初期段階では、バリデーターが「m2o field X has no popup binding」というエラーを出しますが、ユーザーは次のプッシュで修正する予定です)。この状態では、リコンシリエーターが仕様エラーを自動的に回避します。popups/*.yaml が1つでも存在するようになったら、--copy を外してバリデーションを実行します。
差分編集 — 既存のワークスペース
- 追加:ブロック/フィールド/アクション/ポップアップを追加する場合は、DSLを記述してプッシュします。
- 削除:DSLから削除してプッシュします。リコンシリエーターはNB側の対応するライブモデルを削除し、
state.yamlをクリーンアップします。NB-UIで手動作成された要素(state.yamlで追跡されていないもの)はそのまま残ります。 - 名前変更:自動ではサポートされていません。削除してから追加してください。
対象を絞ったプッシュ:
--group <key>で1つのメニューサブツリーを対象にします--page <key>で1つのページを対象にします--incrementalで、DSLが前回のプッシュから変更されていないページをスキップします
DSLのコミットを伴わない、ライブUIだけの調整では、代わりに
nocobase-ui-builder に引き継いでください(上記のルーティング表を参照)。
高度なワークフロー — スターターでは不十分な場合
アジャイルなスターターループから先に進む条件:
- 相互リレーション(m2m、ツリー構造)を持つコレクションが約3つ以上ある
- ユーザーが事前に設計したい専用のワークフロー/承認/権限ロジックがある
- 複数タブのページ、サブテーブル、またはモジュール間ナビゲーションがある
- ユーザーのドメイン用語に対応付けた、独自のKPIを含むダッシュボードがある
進行手順:ラウンド0の設計 → ラウンド1のスキャフォールド → ラウンド2の入力 → ラウンド2'のシード → ラウンド3のJS。それぞれのラウンドはデプロイ可能な状態です。
ラウンド0:システムアーキテクチャ — ユーザーに確認する
DESIGN.md(YAMLではなくMarkdown)を作成し、次の内容を記載します。
- コレクション — すべてのテーブルと、そのフィールドおよびリレーション。
フィールドインターフェイスの参考情報は
nocobase-data-modelingスキルを参照してください。 - ページ一覧 — すべてのページと、それぞれの目的を1行で記載し、メニューごとにグループ化します。
- ナビゲーションの配線 — どのm2oフィールドがどのポップアップテンプレートを開くか、またどのページ同士がリンクするかを記載します。
YAMLを記述する前に、ユーザーの確認を待ちます。設計を一度行うだけで、再設計を3回行う手間を省けます。
ユーザーの依頼がスターターの構成に収まる場合(単一エンティティ、基本的なCRUD)は、ラウンド0を省略します。
例:
## Collections
- nb_lib_books (title, author, isbn, category, status, loans: o2m → nb_lib_loans)
- nb_lib_members (name, email, phone, join_date, loans: o2m → nb_lib_loans)
- nb_lib_loans (loan_no, book: m2o, member: m2o, borrowed_at, due_date, returned_at, status)
## Pages (under menu "Library")
- Books list — browse + search books, add new
- Members list — browse members, their loan history
- Loans list — active/overdue loans, return action
- Dashboard — KPIs + charts
## Navigation
- books.table.title → books detail popup (shared template)
- loans.table.book → books detail popup (shared via defaults.yaml)
- loans.table.member → members detail popup (shared via defaults.yaml)
ラウンド0.5:サブエージェントのCWD(起動する場合のみ)
サブエージェント(kimi TUI、Claude Codeのサブプロセス)を起動すると、そのCWDがデフォルトの書き込み先になります。起動前に設定してください:
mkdir -p <user-workdir>
cd <user-workdir>
kimi --yolo # or claude, codex
cd を省略すると、エージェントは親プロジェクトのルートに書き込みます。
ラウンド1:スキャフォールド — まずはスターターから始める
高度な手順でも、routes.yaml と
collections/*.yaml を最初から手書きしないでください。スターターをコピーしてから拡張します。
| 手順 | 作業内容 |
|---|---|
| ベース | cp -r templates/starter workspaces/<name> — 1回プッシュします |
| コレクションを追加 | collections/<next_coll>.yaml を記述します(形式はスターターに合わせます)。nb_<module>_<entity> の規則に合わせます。 |
| ページを追加 | mkdir pages/<module>/<page>/ + layout.yaml。スターターのprojectsレイアウトを参考にします。 |
| ルートを更新 | routes.yaml の既存グループにエントリーを追加します。 |
| デプロイ | コレクション/ページを追加するたびに cli push <name> --force を実行します |
CRMの参考資料(構造に迷った場合のみ参照):
templates/crm/collections/nb_crm_leads.yaml— コレクション形式templates/crm/routes.yaml— 複数グループのルート(構成のみ)
ラウンド 2:コンテンツを埋める — ブロック、ポップアップ、テンプレート
スターターの基本を超える各ページについて:
| 作成するもの | CRM の参照先 |
|---|---|
| メインリストのテーブル+フィルター | templates/crm/pages/main/leads/layout.yaml |
| 複数タブのページ | templates/crm/pages/main/customers/(page.yaml + tab_*/layout.yaml) |
| o2m の子要素用インラインサブテーブルを含む作成フォーム | templates/crm/templates/block/form_add_new_opportunities_quotations_quotations.yaml — items は fields: に記載された o2m フィールドで、編集可能なインラインサブテーブルとして表示されます。マスター/子要素の UX については templates/crm/pages/main/products/ も参照してください。 |
| 詳細ポップアップテンプレート | templates/crm/templates/popup/activity_view.yaml |
| m2o の自動ポップアップバインディング | templates/crm/defaults.yaml |
| 親の詳細+子リストのポップアップ | templates/crm/pages/main/customers/tab_customers/popups/ |
| addNew +フィールドクリックポップアップのパターン | templates/crm/pages/main/leads/popups/ |
CRM からコピーする際のルール:
- 10~30 行をコピーし、名前を調整してください。ファイル全体はコピーしないでください。
uid:/targetUid:/route_id:はコピーしないでください。デプロイヤーが新しい値を割り当てます。- テーブルに表示するすべての m2o フィールドについて、
clickToOpen: templates/popup/popup_detail_<target>.yamlを設定するか、defaults.yamlにpopups.<target>: ...を追加してください。そうしないと、バリデーターでエラーが発生します。
行ごとのアクション(recordActions)
デフォルトでは、テーブルの行アクション列は空です。recordActions: に列挙しない限り、NB はアクションボタンを描画しません。「編集と削除だけ」はよくあるものの不十分なデフォルト設定です。ほとんどのリストテーブルでは、ユーザーは実際には、編集の隣にワンクリックで状態を変更する操作(完了にする、承認、アーカイブ)を求めています。
判断の順序:
- ブール値または列挙型のステータスフィールドはありますか? →
linkageRulesを指定したupdateRecordを追加し、レコードが適切な状態のときにだけ対応するボタンを表示します。状態遷移ごとにボタンを 1 つ用意します(すでに完了している場合は「完了にする」を非表示、未完了の場合は「再開」を非表示)。 - デフォルトの編集ポップアップとは異なる、2 つ目の詳細/フォームビューが必要ですか? →
templateRefを指定したpopupを使用します。 - ツリー/階層構造のコレクションですか? →
addChildを追加します。 - このレコードの ID/フィルターを使って別の場所へ移動する必要がありますか? →
url: /admin/...?filter={{ctx.record.id}}を指定したlinkを使用します。 - 複雑なレコードをユーザーが複製できるようにしますか? →
duplicateを使用します。 - ワークフローを手動で開始する必要がありますか? →
workflowTriggerを使用します。
状態変更には、カスタム JS ボタンより updateRecord + linkageRules を優先してください。linkageRules を使えば、JS に触れることなく、行レベルの UX の 80%(条件付きの表示/非表示、フィールドに基づく制御、ctx.user を使ったロールチェック)に対応できます。JS アクションが必要なのは、複数ステップのロジック(クエリしてから更新し、その後に移動)を行う場合のみです。また、現在このリコンサイラーではデプロイできません。
行ごとのアクションの一覧(recordActions: で宣言):
DSL の type | 目的 | CRM の参照先 |
|---|---|---|
edit | 編集ポップアップ(デフォルトの形式) | 多数 |
view | 読み取り専用の詳細ポップアップ | templates/crm/templates/popup/opportunity_view.yaml |
delete | 確認付きの単一行削除 | 多数 |
updateRecord | フィールドを割り当てる(linkageRules は任意)— 状態変更の主役 | templates/crm/pages/main/overview/layout.yaml(完了/未完了のペア) |
popup | templateRef を使ってカスタムポップアップを開く(編集とは異なるフォーム/詳細) | templates/crm/pages/main/leads/popups/table.name.yaml |
link | レコードのコンテキストを URL に含めて、別の管理ページへ移動 | templates/crm/pages/lookup/layout.yaml |
addChild | ツリーコレクション:この行の下に子ノードを追加 | templates/crm/pages/main/products/tab_categories/layout.yaml |
duplicate | レコードを新しいフォームに複製 | — |
workflowTrigger | このレコードのワークフローを手動でトリガー | (templates/crm/pages/main/customers/tab_customers/layout.yaml のツールバー — 同じ形式を行ごとにも使用できます) |
historyExpand / historyCollapse | レコード履歴プラグインのインライン展開 | — |
ai | AI 従業員ボタン(tasks_file + employee) | templates/crm/pages/main/leads/layout.yaml(ツールバー — 同じ形式を行ごとにも使用できます) |
ツールバーアクション(ブロックレベルの actions: で宣言)は、同じタイプ名に加えて filter、refresh、addNew、bulkDelete、export、import を使用します。すべてのタイプがどちらのコンテキストでも使えるわけではありません。ツールバーアクションとしての updateRecord は特定の行に適用されないため、recordActions に指定してください。
Round 2 の目標:すべてのページで CRUD を利用できるようにします。追加/編集/表示ポップアップを正しく接続し、行アクション列に実際のレコードごとの操作を反映します(編集と削除だけにしない)。バリデーターのエラーをなくし、NB UI に「コレクションが削除された可能性があります」というバナーが表示されないようにします。
Round 2':テストデータ(Round 2 と並行)
ページの作成と並行して実行できます。API 経由でデータを挿入します:
TOKEN=$(curl -sS -X POST $NB_URL/api/auth:signIn \
-H 'Content-Type: application/json' -H 'X-Authenticator: basic' \
-d '{"account":"'$NB_USER'","password":"'$NB_PASSWORD'"}' \
| python3 -c 'import json,sys;print(json.load(sys.stdin)["data"]["token"])')
# Always GET existing record IDs first — they're snowflake integers
# (e.g. 359571523764224), NEVER 1/2/3.
curl -sS -X POST $NB_URL/api/<collection>:create -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{...fields..., "owner":{"id": <real-user-id>}}'
親テーブルから先に挿入し、子テーブルではすべての FK を設定します(null のままにすると、その行は孤立します)。その後:
npx tsx cli/cli.ts verify-data <name> # FK & completeness check
並行して行う理由:Round 3 の JS(チャート、KPI)で何かを表示するにはデータが必要です。コレクションができたら(Round 1 の終わり)、シード処理を開始してください。Round 2 でページの作成が終わる頃には、テスト用のレコードが揃っています。
ラウンド 3:JS — CRM で使われている場所では、あなたのプロジェクトでも必要になる可能性があります
CRUD とデータ処理が動作したら、JavaScript が役立つ場所を確認します。CRM テンプレートを見て、プロジェクト内の各箇所について「CRM ではここに JS があるか?」と確認してください。 JS がよく使われる箇所は次の 3 つです。
| 箇所 | CRM に JS はあるか? | 次に当てはまる場合、プロジェクトでも JS が必要になる可能性があります |
|---|---|---|
| フィールドレンダラー/列(例:色分けされたステータスタグ、期限までの日数を示すバッジ) | ✅ ほとんどのリストテーブルにあります | 派生値(日付計算、ステータスに応じた色分け、複数フィールドの合成)によって表示が変わるフィールドがある |
| ブロック(KPI カード、フォーム内のカスタムウィジェット) | ✅ 概要、分析、フォームごとのヒントにあります | 複数のコレクションから読み込むサマリーウィジェット、インラインチャート、「ヘルパーパネル」が必要 |
| ダッシュボードページ(チャートと KPI を並べたページ全体) | ✅ 分析ページにあります | モジュールにユーザーが重視する測定可能な指標が 3 つ以上ある。バリデーターでは、「Dashboard」/「Analytics」というタイトルのページに 5 つ以上のチャートが必須です |
まず、該当する CRM ページとその js/ および charts/ フォルダーを grep して、CRM が JS を追加している場所を確認します。次に、下の単一ファイルの表を参考にして、プロジェクト用の JS ファイルを作成します。
ダッシュボードを独自に設計すると見栄えが悪くなりがちです。CRM の構成を参考にしてください。 まず参照レイアウトを開き、ブロック数、順序、グリッド幅を自分の layout.yaml にコピーしてから、各リーフファイルにコンテンツを追加します。
| 参照レイアウト | 参考にする内容 |
|---|---|
templates/crm/pages/main/overview/layout.yaml | 概要:1 つの jsBlock ヒーロー行、その下に小さなテーブルが 2 つあります。少数の KPI を表示するランディングページに使います。 |
templates/crm/pages/main/analytics/layout.yaml | フルダッシュボード:filterForm の行 → 1 行に 4 つの KPI jsBlock → 16/8 ∣ full ∣ 14/10 グリッドに 5 つのチャートがあります。5 つ以上のチャートを表示する場合に使います(ページタイトルに dashboard または analytics が含まれる場合、バリデーターで必須です)。 |
手順:
- 参照用の
layout.yamlを開きます。ブロックのキー/タイプ/幅を確認します。 - 同じ構成でプロジェクト用の
layout.yamlを作成します。ブロック数とlayout:セクションのグリッド幅は同じにし、ブロックキーとコレクション名は独自のものにします。 - 各ブロックのリーフ JS/SQL ファイルは、下の表からコピーします。ファイルは個別にコピーしてください。フォルダーを
cp -rでコピーしないでください。
| コピーするリーフファイル | 参照元 |
|---|---|
| KPI カード jsBlock | templates/crm/pages/main/overview/js/overview_jsBlock.js |
| フィルター済みサマリー jsBlock | templates/crm/pages/main/analytics/js/analytics_jsBlock.js |
| チャート SQL(グループ化した件数) | templates/crm/pages/main/analytics/charts/analytics_chart_2.sql |
| チャートの描画(echarts の棒グラフ/円グラフ) | templates/crm/pages/main/analytics/charts/analytics_chart_2_render.js |
| filterForm のフィルター統計ボタン | templates/crm/pages/main/customers/tab_customers/js/customers_customers_filterForm_customer_stats_filter_block.js |
| ページ全体のカスタム UI(ウィザード/複数ステップ/カスタムフロー) | templates/crm/pages/main/customers/tab_merge/js/customers_merge_jsBlock.js — ページ全体が 1 つの type: jsBlock で構成されており、React で約 580 行です |
各リーフファイルをコピーした後:
- コピー先で名前を変更し、SQL/コレクション/フィールド名を適切なものに置き換えます。
- モジュールに i18n がない場合は、
ns: 'nb_crm'の i18n ラッパーを削除します。 - ページの filterForm で使うフィールドキーが異なる場合は、
ctx.var_form1.*のフィルター変数参照を簡略化します。
SQL チャート:保存してから実行する 2 段階のパターンを使います —
ctx.sql.save({uid, sql}) の後に ctx.sql.runById(uid) を呼び出します。
基本概念
2 つの識別子:key と title
key = lower_snake_ascii 形式の識別子です。pages/ 配下のディレクトリ名と state.yaml のエントリーを決定します。タイトルが ASCII 文字のみでない場合(中国語やスペースを含み、スラッグ化すると意味不明になる場合)は、必ず明示的に指定してください。
title = ユーザーに表示するテキストです。
- key: it_ops
title: IT 运维
type: group
children:
- key: tickets
title: 工单
2 種類のポップアップモード:key: reference と単独の ref:
key: reference — ポップアップブロックはテンプレートへの参照です。
テンプレートを編集すると、それを参照しているすべてのポップアップが更新されます。共有の追加/編集フォームにはこちらを使用してください。
blocks:
- ref: templates/block/form_add_new_tickets.yaml
key: reference # REQUIRED for shared refs
単独の ref:(key: reference なし)— テンプレートの内容はポップアップごとにインライン展開され、各コピーは独立しています。ページファイルから大きなブロックを切り出す場合にのみ使用してください。
デプロイ後、共有テンプレートの usageCount は 1 以上になるはずです。0 のままの場合は、key: reference を指定し忘れています。
自動作成されるカラム — 宣言しないでください
NocoBase は次のカラムを自動作成します。宣言すると、通知なしにフィルタリングされたり、型の競合が発生したりします。
- システムカラム:
id、createdAt、updatedAt、createdBy、updatedBy - m2o / o2m の外部キー(FK)カラム:
owner: m2o → usersを宣言するとowner_idが自動作成されます。2 つ目のowner_id: integerの行を追加しないでください - m2m の結合テーブル:
through: nb_x_yは自動作成されます。そのため、このテーブル用のコレクション YAML は記述しないでください
テーブルとサブテーブル — 異なるものなので、混同しないでください
| テーブル | サブテーブル | |
|---|---|---|
| 概要 | 親レコードの子レコード用の完全な CRUD ブロック(フィルター+一覧+追加/編集ポップアップ) | 親フォームの内部にある、子行をインライン編集するグリッド |
| DSL | type: table + resource_binding.sourceId + associationName | createForm/editForm の fields: 内にある { field: tasks, type: subTable, columns: [...] } |
| 使用場所 | 詳細ポップアップ、タブページ、独立した一覧ポップアップ | createForm/editForm の内部 |
| 使用する場面 | 子レコードを個別に参照する場合(顧客詳細 → 注文一覧) | 子レコードを親と一緒に入力する場合(請求書+明細) |
フォーム内の単独の - tasks は 3 つ目の選択肢である RecordSelect ピッカー
(「既存のレコードを選択」)です。通常は意図したものではなく、バリデーターが警告を表示します。
標準的な CRM の例:
- テーブル(独立した CRUD ブロック):
templates/crm/pages/main/customers/tab_customers/popups/table.name.yaml - サブテーブル(インライン編集可能なグリッド):
templates/crm/templates/block/form_add_new_opportunities_quotations_quotations.yaml(items)
foreignKey で意味が切り替わります
m2o では、foreignKey は現在のテーブルにある FK カラムの名前です
(owner: m2o, foreignKey: owner_id → SELF 上の owner_id)。
o2m では、foreignKey は対象テーブルにある FK カラムの名前です
(tasks: o2m → nb_pm_tasks, foreignKey: project_id → nb_pm_tasks 上の project_id)。
コマンドリファレンス
cd <skill-dir>/src
export NB_USER=... NB_PASSWORD=... NB_URL=...
npx tsx cli/cli.ts push <name> --force # deploy DSL → NocoBase
npx tsx cli/cli.ts push <name> --group <key> # only one subtree
npx tsx cli/cli.ts push <name> --incremental # skip unchanged (git diff)
npx tsx cli/cli.ts pull <name> # NocoBase → DSL (full round-trip)
npx tsx cli/cli.ts diff <left> <right> # compare two DSL trees
npx tsx cli/cli.ts duplicate-project <src> <dst> --key-suffix _v2
npx tsx cli/cli.ts verify-data <name> # FK / completeness check
push と pull はどちらも一方向です。往復処理は push + pull + git diff です。
よくあるエラー
| エラー | 対処方法 |
|---|---|
fields not in collection | フィールド名がコレクション YAML と一致していません |
titleField is missing | titleField: <field> を設定するか、name/title フィールドを追加してください |
| 一部のページしかデプロイされない | key が pages/<key>/ ディレクトリと一致していません |
作成時に string violation が発生する | YAML で createdAt/updatedAt が宣言されています — 削除してください |
| チャートの SQL が失敗する | 先にシードデータを投入してください。"createdAt" のようにフィールド名を引用符で囲んでください |
| UI で m2o リンクが 400 になる | 対象コレクション用の defaults.yaml の popups: バインディングがありません |
Collection X not found in data source main | associationName に短縮名が使われています — コレクション名全体(project.tasks ではなく nb_pm_projects.tasks)を使用してください。templates/crm/pages/main/customers/tab_customers/popups/ を参照してください |
| 行ごとのカラムに編集と削除しか表示されない | recordActions がありません — 追加する内容については、以下の「行ごとのアクション」を参照してください。DSL からアクション/フィールド/カラムを削除すると、次回の push で NB 側からも削除されるようになりました。 |
上記の内容と実行時の挙動が異なる場合、マニュアルが古くなっています。不足している内容を記録し、ユーザーに伝えてください。