domaintrail 0.4.0 → 0.6.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.
@@ -0,0 +1,303 @@
1
+ # 変更履歴
2
+
3
+ [English](CHANGELOG.md)
4
+
5
+ domaintrail の各バージョンの変更点です。形式は [Keep a Changelog](https://keepachangelog.com/ja/) に、バージョン番号は
6
+ [セマンティック バージョニング](https://semver.org/lang/ja/) に従います(1.0.0 より前は、マイナーバージョンでもモデルやコードに
7
+ 影響する変更があります)。
8
+
9
+ **domaintrail を更新したら**、更新前から更新後までの各バージョンの **アップグレード手順**を古い順に読み、その手順に
10
+ 従ってください。このファイルはパッケージに含まれています: `node_modules/domaintrail/CHANGELOG.ja.md`(英語版は
11
+ `CHANGELOG.md`)。
12
+
13
+ ## [0.6.0] - 2026-10-28
14
+
15
+ ### 追加
16
+
17
+ - **Live View を git のリビジョンと比較**(`docs/live-view.md`): ヘッダーの 比較対象 メニュー、または変更ペインの見出しから、
18
+ 追加・変更・削除を何と比べて表示するかを選べます — 前回の保存(既定)か、git のリビジョン(ブランチ、タグ、最近のコミット、
19
+ または入力した任意のリビジョン)です。`dt view --base <ref>` で最初から指定して起動できます。図はノードのフィールドに、
20
+ インスペクターは 構造・状態遷移・振る舞い タブの行に印を付けます(追加、変更、削除は取り消し線、変更されたものは変更前の値も
21
+ 表示)。
22
+ - **Getting started** を英語と日本語で追加しました(`docs/getting-started.md`、`docs/getting-started.ja.md`)。README は
23
+ それを中心に再構成しました。
24
+ - 変更履歴をパッケージに含めました(`node_modules/domaintrail/CHANGELOG.md`、`CHANGELOG.ja.md`)。`dt guide` は、更新後に
25
+ そのアップグレード手順に従うようエージェントに伝えます。
26
+
27
+ ## [0.5.0] - 2026-10-24
28
+
29
+ ### 追加
30
+
31
+ - **ポリシー**(`dt guide reactions`、ADR 0022): `policy P when Event -> Aggregate.command` — イベントが起きたら、
32
+ ポリシーを宣言したコンテキスト自身のコマンドがいずれ実行される、という反応を書けます。M023 がイベント・集約・コマンドを
33
+ 検査し、同じ反応の重複を警告します。ポリシーごとに、イベントからコマンドの入力(`<Command><Aggregate>Input`)を作る
34
+ ハンドラの型が生成されます。`generate.ts.policies: "required"`(既定は `"optional"`、`generate.ts.contexts` で
35
+ コンテキストごとにも指定可)にすると `<Context>PolicyHandlers` が生成され、契約テストがそれを受け取り、どのイベントも
36
+ コマンドが受け付ける入力になることを検査します。コンテキストマップはポリシーをイベントによる依存として数えます。
37
+ - **検証済み型**(`dt guide verified`、ADR 0023): `verified X of T for C? errors E` — 集約の外で行った検査(他の誰も
38
+ 使っていないメールアドレスなど)の証拠です。作れるのは利用者が書く非同期ファクトリ `Make<Name>` だけで、コマンドは
39
+ それを引数として受け取ります。M024 は検証済み型をコマンドの引数に限ります。
40
+ - **ペイロードの名前**(ADR 0021): イベントやエラーのフィールドが、名前の違う値(引数、そのフィールド、コマンド前後の
41
+ 集約のフィールド)から値を取っていそうなときに M022 が警告します。
42
+ - **Zed 拡張**(`editors/zed`)と **tree-sitter 文法**(`tree-sitter-domaintrail`): Zed でのハイライト、アウトライン、
43
+ `dt lsp`。tree-sitter を使う他のエディタ向けの文法(`dt guide editors`)。
44
+ - **キャストを防ぐ lint**(`dt guide lint`): どのドメイン型も `as` を使わずに作る方法と、oxlint または
45
+ typescript-eslint で `no-unsafe-type-assertion` を有効にする方法。oxlint / ESLint の設定がこのルールに触れていないとき、
46
+ `dt generate ts` がヒントを表示します(`--format json` では `hints`)。`"lintHint": false` で止められます。ID、
47
+ ドメインプリミティブ、検証済み型の JSDoc に、それぞれの作り方を書きました。
48
+ - 生成されるランタイムに `parseDecimal(text)`: `Decimal` を作る唯一の場所です。
49
+ - `dt guide policies`: 変わりうる方針(ポリシー)に依存するルールの書き方。
50
+ - `examples/shop` を完全なプロジェクトにしました: すべての集約の実装、アプリケーション層(ユースケース、リポジトリ、
51
+ イベントストア、ポリシー、検証済み型のファクトリ)、テスト、デモ。README で説明しています。
52
+ - 言語サーバー: コマンドの名前を変更(リネーム)できます。
53
+
54
+ ### 変更
55
+
56
+ - ワークスペースのファイルを集めるすべてのコマンド(`check`、`generate`、`view`、`fmt`、`show`、`lsp`)は、
57
+ `.gitignore`(ルート、そのサブディレクトリ、リポジトリの最上位までの親ディレクトリのもの)と `.git/info/exclude` が
58
+ 無視するファイルを読み飛ばします。`"respectGitignore": false` で無効にできます。コマンドラインで指定したファイルは
59
+ 読み飛ばしません。
60
+ - 言語サーバーが設定ファイルを見つけられないとき、設定を持つディレクトリの下で最初に開いた `.dt` ファイルによって、
61
+ その設定がワークスペースになります。
62
+
63
+ ### 修正
64
+
65
+ - `Int` や `Decimal` を基にするドメインプリミティブを、`count()` や `length()` が計算する数と比較できるようにしました。
66
+
67
+ ### アップグレード手順
68
+
69
+ 1. モデルの `.dt` ファイルが `.gitignore` で無視されるディレクトリにあると、ワークスペースに含まれなくなります。
70
+ ファイルを移すか、`domaintrail.jsonc` に `"respectGitignore": false` を書いてください。`dt check` を実行し、
71
+ ファイル数が以前と同じか確かめます。
72
+ 2. M011: `parseDecimal` はランタイムの名前になりました。その名前の要素がモデルにあれば名前を変えてください。
73
+ 3. `dt generate ts` を実行します。lint のヒントが出たら `dt guide lint` に従い(または `"lintHint": false`)、
74
+ コード中のドメイン型へのキャストを `make<Name>` / `parseDecimal` に置き換えます。
75
+ 4. 新しい警告が出ることがあります: M022(イベントやエラーのフィールドを値の出どころに合わせて名前を変えるか、
76
+ 出どころの名前をフィールドに合わせる)。警告では `dt check` は失敗しません。
77
+
78
+ ## [0.4.0] - 2026-10-24
79
+
80
+ ### 追加
81
+
82
+ - コマンドの **`requires`**(`dt guide requires`、ADR 0017): `requires name { expr } else Error` — 式が成り立つときだけ
83
+ コマンドを受け付け、成り立たなければそのエラーを返します(M019)。
84
+ - コマンドの **`changes`**(`dt guide changes`、ADR 0019): コマンドが変更してよいフィールド(M021)。
85
+ - **多重度と `when` を持つ `emits`**(`dt guide events`、ADR 0018): `X?`、`X[0..*]`、`X[1..*]`、`X[n..m]`、
86
+ 一部の結果でだけ発生するイベントには `X when Draft ->` / `X when -> Completed`。
87
+ - 式の **`unique` と `contains`**、および式を持たず言葉だけで書く不変条件(M018、ADR 0016)。
88
+ - **決定(decision)と `decide`**(`dt guide decide`): 各コマンドは `<Command><Aggregate>Decision` として実装します —
89
+ 遷移元の状態ごとに、結果の `set` と `events` を返し、ランタイムの `decide(<Aggregate>Shape, decision)` が集約を
90
+ 作ります。足りないイベント、余分なイベント、変更してはいけないフィールドを、コンパイラが名前を挙げて報告します。
91
+ - **生成される検査とコンストラクタ**(ADR 0020): 不変条件と要件の式から `check<Name>` /
92
+ `check<Command><Aggregate>Requires`、すべての ID とドメインプリミティブに `make<Name>`。
93
+ - **生成される契約テスト**(`dt guide tests`): `generate.ts.tests`(`runner`: `bun`、`vitest`、`jest`、`node`。
94
+ `out`)を指定すると、`dt generate ts` がコンテキストごとに fast-check のスイートを書き出し、コンストラクタ、要件、
95
+ 各コマンド後の不変条件、イベント、純粋性を検査します。プロジェクトに `fast-check` が必要です。
96
+ - **`dt fmt`**: `.dt` ファイルを、コメントを保ったまま一つの正規のレイアウトに整形します(CI 向けに `--check`)。
97
+ LSP の整形にも使われます。
98
+ - **`dt show <name>`**: 要素・状態・コマンド・コンテキストを一つ、解決した形で、使っている側とともに表示します
99
+ (`--format json`)。
100
+ - **`dt lsp`**: `dt check` の診断、定義へ移動、参照、ホバー、アウトライン、補完、リネームを提供する言語サーバー。
101
+ - 診断の**ヒント**(「もしかして …」や、何を変えればよいか)と、`check` と `generate ts` の `--format json`
102
+ (スキーマは `schema/diagnostics.schema.json`)。
103
+ - 生成されるすべての宣言に、DSL の位置を指す `@see <file>:<line>`。
104
+ - どのコマンドも扱うコード向けのランタイムヘルパー `outcome(value)`(`{ to, aggregate, events }`)と
105
+ `eventList(events)`。
106
+ - M020(警告): 集約の ID 型の引数を持たない `create command`。
107
+
108
+ ### 変更
109
+
110
+ - **`emits X` は、コマンドが成功したときにちょうど 1 つの `X`** を意味します(以前は「発生させうる」)。エラーのときは
111
+ 何も発生させません。
112
+ - **コマンドの結果は結果(遷移先)ごとのキーを持つ形**になりました: タプル `[aggregate, events[]]` の代わりに
113
+ `{ Confirmed: { aggregate, events: { OrderConfirmed } } }`。イベントは名前をキーとし、順序を持ちません。イベントの
114
+ ないコマンドは `NoEvents` です。
115
+ - コマンドは純粋関数です: ID と時刻は引数として受け取ります(ファクトリは自分の ID を受け取ります)。
116
+ - `ok` は契約からだけ型を推論するので、リテラル中の余分なイベントが報告されます。
117
+ - `changes` のないコマンドは、共通フィールドを変更しません。
118
+ - 生成されるモジュールが値(`<Aggregate>Shape`)をエクスポートします: `import type` ではなく値として import します。
119
+ - `dt guide` はクイックリファレンスとトピック一覧を表示します。`dt guide <topic>` で 1 節、`dt guide <rule>` で 1 ルール、
120
+ `dt guide --all` でガイド全体。
121
+ - `[0..1]` は `?` と、`[1..1]` は多重度なしと同じ意味になりました。
122
+ - Live View は各イベントの多重度と `when`、`requires` と `changes`、問題ペインのヒントを表示します。
123
+
124
+ ### 削除
125
+
126
+ - `byState` と `<Command><Aggregate>ByState` 型(0.3.0): すべてのコマンドを `decide` で実装します。
127
+
128
+ ### アップグレード手順
129
+
130
+ まずモデルを変え、次に生成し、最後にコードを直します。規則の詳細は `dt guide events`、`dt guide changes`、
131
+ `dt guide decide` にあります。
132
+
133
+ 1. **イベント。** `emits X` は、成功するたびにちょうど 1 つの `X` になりました。`emits` の各項目について、そのイベントが
134
+ いつ発生するかを決めて、そのとおりに書きます:
135
+
136
+ ```dt
137
+ // 変更前(0.3.0): 「発生させうる」
138
+ command changeNote(note: String?)
139
+ Draft -> Draft, Confirmed -> Confirmed
140
+ emits NoteChanged
141
+ command cancel(reason: CancelReason)
142
+ Draft | Confirmed -> Cancelled
143
+ emits OrderCancelled, RefundRequested
144
+ // 変更後(0.4.0)
145
+ command changeNote(note: String?)
146
+ Draft -> Draft, Confirmed -> Confirmed
147
+ changes note
148
+ emits NoteChanged? // 高々 1 つ。いくつでもなら X[0..*]
149
+ command cancel(reason: CancelReason)
150
+ Draft | Confirmed -> Cancelled
151
+ emits OrderCancelled,
152
+ RefundRequested when Confirmed -> // Confirmed からのときだけ
153
+ ```
154
+
155
+ 2. **ファクトリは ID を、コマンドは時刻を受け取ります。** 各 `create command` に集約の ID 型の引数を加え(加えるまで
156
+ M020 が警告します)、実装が時計を読んでいたところには `Timestamp` の引数を加えます:
157
+
158
+ ```dt
159
+ create command place(customer: CustomerId) -> Draft // 変更前
160
+ create command place(id: OrderId, customer: CustomerId) -> Draft // 変更後
161
+ ```
162
+
163
+ ID は呼び出し側が作って(`make<Name>(crypto.randomUUID())`)渡します。
164
+ 3. **`changes`。** 遷移元と遷移先の状態に共通するフィールド(状態のない集約ではすべてのフィールド)を変更するコマンドに、
165
+ `changes f1, f2` を加えます。書かないと、生成される決定の型がそのフィールドの設定を許しません。
166
+ 4. **必要なら `requires`。** 何かを変える前にエラーを返す検査は `requires name { expr } else Error` にできます。生成される
167
+ `check<Command><Aggregate>Requires` と契約テストがそれを検査します。
168
+ 5. **予約された名前(M011)。** `decide`、`Decide`、`Shape`、`NoChanges`、`outcome`、`eventList`、`NoEvents`、
169
+ `Outcome`、`EventOf`、`sameOptional`、`sameList`、`distinct` はランタイムの名前に、`<Command><Aggregate>Decision`、
170
+ `<Aggregate>Shape` は生成される名前になりました: 衝突する要素の名前を変えます。`dt check` が 0 で終わるまで直します。
171
+ 6. **生成し直し、すべてのコマンドを `decide` で書き直します。** 結果の `set`(`changes` のフィールドと遷移先の状態で
172
+ 新しく持つフィールド。ファクトリはすべてのフィールドを設定し、省略可能なものは `undefined`)と `events` を、遷移先の
173
+ 状態をキーにして返します(状態のない集約では集約名がキー)。遷移元が一つでも、遷移元の状態ごとにハンドラを書きます。
174
+ ファクトリは一つの関数です:
175
+
176
+ ```ts
177
+ // 変更前(0.3.0)
178
+ import { byState, ok, type Sales } from '../generated/domain/index.js';
179
+ export const orderCommands: Sales.OrderCommands = {
180
+ place: (customer) => {
181
+ const order: Sales.DraftOrder = { state: 'Draft', id: crypto.randomUUID() as Sales.OrderId, items: [], customer };
182
+ return ok([order, [{ type: 'OrderPlaced', orderId: order.id }]] as const);
183
+ },
184
+ confirm: (order, at) =>
185
+ ok([{ ...order, state: 'Confirmed', confirmedAt: at }, [{ type: 'OrderConfirmed', orderId: order.id, confirmedAt: at }]] as const),
186
+ changeNote: byState<Sales.ChangeNoteOrderByState>({
187
+ Draft: (order, note) => ok([{ ...order, note }, []] as const),
188
+ Confirmed: (order, note) => ok([{ ...order, note }, []] as const),
189
+ }),
190
+ };
191
+
192
+ // 変更後(0.4.0)
193
+ import { Sales, decide, ok } from '../generated/domain/index.js';
194
+ export const orderCommands: Sales.OrderCommands = {
195
+ place: decide<Sales.PlaceOrderDecision>(Sales.OrderShape, (id, customer) =>
196
+ ok({ Draft: { set: { id, items: [], customer, note: undefined }, events: { OrderPlaced: { type: 'OrderPlaced', orderId: id } } } }),
197
+ ),
198
+ confirm: decide<Sales.ConfirmOrderDecision>(Sales.OrderShape, {
199
+ Draft: (order, at) =>
200
+ ok({ Confirmed: { set: { confirmedAt: at }, events: { OrderConfirmed: { type: 'OrderConfirmed', orderId: order.id, confirmedAt: at } } } }),
201
+ }),
202
+ changeNote: decide<Sales.ChangeNoteOrderDecision>(Sales.OrderShape, {
203
+ Draft: (order, note) => ok({ Draft: { set: { note }, events: {} } }), // `emits NoteChanged?` なら、備考が変わったときに渡す
204
+ Confirmed: (order, note) => ok({ Confirmed: { set: { note }, events: {} } }),
205
+ }),
206
+ };
207
+ ```
208
+
209
+ 7. **呼び出し側**はキー付きの結果を読みます: `result.value.Confirmed.aggregate` / `.events.OrderConfirmed`。どのコマンドも
210
+ 扱うコードでは `outcome(result.value)` → `{ to, aggregate, events }` と `eventList(events)` を使います。
211
+ 8. 型検査します(`tsc --noEmit`)。契約と実装が食い違うとき — いつも発生させられるとは限らないイベント、変更が必要な
212
+ フィールド — は、実装ではなく DSL を変えます(`X?`、`changes`)。
213
+ 9. 必要なら契約テストを有効にし(`generate.ts.tests`、`dt guide tests`)、一度 `dt fmt` を実行します。
214
+
215
+ ## [0.3.0] - 2026-10-19
216
+
217
+ ### 追加
218
+
219
+ - 遷移元の状態ごとのハンドラ: 遷移先が遷移元の状態によって決まるコマンドには `<Command><Aggregate>ByState` も生成され、
220
+ ランタイムの `byState({...})` で実装します(ADR 0015)。
221
+ - 状態遷移図は遷移ごとに辺を描き、インスペクタは遷移ごとに行を表示します。
222
+
223
+ ### 変更
224
+
225
+ - **コマンドは `from … to …` の代わりに遷移を並べます**(ADR 0014): `Draft -> Confirmed`、
226
+ `Draft | Confirmed -> Cancelled`、`Draft -> Draft, Confirmed -> Confirmed`。ファクトリは遷移元のない遷移を一つ
227
+ 持ちます(`-> Draft`)。一つの状態は、コマンドごとに一つの遷移の遷移元になります(M006)。`from` と `to` は
228
+ キーワードではなくなりました。
229
+ - 遷移先が遷移元の状態によって決まるコマンドの生成される契約は、入力の状態についてジェネリックになり、`Draft` からは
230
+ `Draft`、`Confirmed` からは `Confirmed` が返ります。
231
+
232
+ ### 修正
233
+
234
+ - `npm publish` が警告しないよう、パッケージの `bin` のパスの書き方を直しました。
235
+
236
+ ### アップグレード手順
237
+
238
+ 1. 各コマンドの `from … to …` を遷移に書き換えます:
239
+
240
+ ```dt
241
+ // 変更前(0.2.0)
242
+ create command place(customer: CustomerId) to Draft
243
+ command confirm(at: Timestamp) from Draft to Confirmed emits OrderConfirmed
244
+ command changeNote(note: String?) from Draft | Confirmed to Draft | Confirmed
245
+ // 変更後(0.3.0)
246
+ create command place(customer: CustomerId) -> Draft
247
+ command confirm(at: Timestamp) Draft -> Confirmed emits OrderConfirmed
248
+ command changeNote(note: String?) Draft -> Draft, Confirmed -> Confirmed // どの遷移元がどこへ行くかを書く
249
+ ```
250
+
251
+ どの遷移元からどの遷移先へも行けるなら、`from A | B to C | D` は `A | B -> C | D` になります。できるだけ正確な
252
+ 遷移を書いてください。
253
+ 2. `dt check`、続いて `dt generate ts` を実行します。遷移先が遷移元の状態によって決まるコマンドは契約がジェネリックに
254
+ なります: 型アサーションの代わりに
255
+ `byState<Ctx.<Command><Aggregate>ByState>({ Draft: (…) => …, Confirmed: (…) => … })` で実装します。
256
+
257
+ ## [0.2.0] - 2026-10-18
258
+
259
+ ### 追加
260
+
261
+ - **項目セット**(`dt guide field-sets`、ADR 0013): `fields Name { ... }` で普通のフィールドの集まりを宣言し、集約・
262
+ エンティティ・イベントが `with A, B` でそれを持ちます(M016)。共有コンテキストにも置けます。要素が継承する
263
+ インターフェースが生成されます。
264
+ - コンテキストごとに生成される union: `DomainEvent`(すべてのイベント)、`Aggregate`(すべての集約)、
265
+ `<Aggregate>Event`(集約のコマンドが発生させるイベント)。
266
+ - M017(警告): 集約のコマンドが発生させるイベントに、その集約の ID 型のフィールドがない。
267
+ - ガイドで、ドメインのフィールドと技術的なフィールドの違いと、技術的なメタデータをエンベロープで付ける方法を説明しました。
268
+ - Live View: カタログのフィルタ(テキスト、種類、コンテキスト)。エクスプローラ、カタログ、インスペクタ、構造図での
269
+ 項目セットの表示。
270
+
271
+ ### 変更
272
+
273
+ - Live View: カタログのカードは、バッジを見出しの上に、ラベルを名前の上に置きます。カタログのセクションへのリンクは
274
+ なくなりました(フィルタでセクションを探せます)。
275
+
276
+ ### アップグレード手順
277
+
278
+ 1. M011: `DomainEvent`、`Aggregate`、`<Aggregate>Event` は生成される名前になりました。これらと名前が衝突する
279
+ コンテキストの要素の名前を変え、`dt check` を実行します。
280
+ 2. `fields` と `with` はキーワードになりましたが、フィールド名や引数名としては引き続き使えます。
281
+ 3. 新しい M017 の警告には、集約の ID をそのイベントに加えて対応します(`orderId: OrderId`)。
282
+
283
+ ## [0.1.0] - 2026-10-18
284
+
285
+ 最初に公開したバージョンです。
286
+
287
+ ### 追加
288
+
289
+ - DomainTrail DSL: 境界づけられたコンテキスト、状態を持つ集約、エンティティ、値オブジェクト、ID、ドメインプリミティブ、
290
+ 列挙型、union、コマンド(`from … to …`)、イベント(`persistent`)、エラー、共有コンテキスト、ラベルと説明、
291
+ 不変条件の式。ルール M001〜M015 と L001〜L003。
292
+ - `dt` CLI(`domaintrail` としてもインストールされます。Node.js 22.12 以降): `dt check`、`dt generate ts`
293
+ (TypeScript の型とコマンドの契約。`generate.ts.contexts` でコンテキストごとの出力先)、`dt view`、`dt guide`。
294
+ - `domaintrail.jsonc`(`sources`、`generate.ts`、`view`)と、その JSON Schema `schema/domaintrail.schema.json`。
295
+ - Live View(`dt view`): コンテキストマップ、構造図(3 段階)、カタログ、状態遷移図、ソースを表示するインスペクタ、問題と
296
+ 変更の一覧。保存のたびに更新されます。日本語と英語、ライトとダーク。
297
+
298
+ [0.6.0]: https://github.com/hisamekms/domaintrail/compare/v0.5.0...v0.6.0
299
+ [0.5.0]: https://github.com/hisamekms/domaintrail/compare/v0.4.0...v0.5.0
300
+ [0.4.0]: https://github.com/hisamekms/domaintrail/compare/v0.3.0...v0.4.0
301
+ [0.3.0]: https://github.com/hisamekms/domaintrail/compare/v0.2.0...v0.3.0
302
+ [0.2.0]: https://github.com/hisamekms/domaintrail/compare/b9f56885cde82f404bbf6b558f43b8ecb6254dfe...v0.2.0
303
+ [0.1.0]: https://github.com/hisamekms/domaintrail/tree/b9f56885cde82f404bbf6b558f43b8ecb6254dfe
package/CHANGELOG.md ADDED
@@ -0,0 +1,304 @@
1
+ # Changelog
2
+
3
+ [日本語](CHANGELOG.ja.md)
4
+
5
+ What changed in each version of domaintrail. The format follows [Keep a Changelog](https://keepachangelog.com/), and
6
+ the versions follow [Semantic Versioning](https://semver.org/) (before 1.0.0, a minor version may break models and
7
+ code).
8
+
9
+ **After upgrading domaintrail**, read the **Upgrading** section of every version between the one you had and the new
10
+ one, oldest first, and follow its steps. This file is in the package: `node_modules/domaintrail/CHANGELOG.md`.
11
+
12
+ ## [0.6.0] - 2026-10-28
13
+
14
+ ### Added
15
+
16
+ - **Comparing the Live View with a git revision** (`docs/live-view.md`): the 比較対象 menu in the header, or the heading
17
+ of the changes pane, picks what additions, changes and removals are shown against — the previous save (the default)
18
+ or a git revision: a branch, a tag, one of the latest commits or any revision typed in. `dt view --base <ref>` starts
19
+ with one. The diagrams mark the fields of their nodes, and the inspector marks the rows of its 構造, 状態遷移 and 振る舞い
20
+ tabs (added, changed, removed struck through, with the former value of a changed one).
21
+ - **Getting started** in English and Japanese (`docs/getting-started.md`, `docs/getting-started.ja.md`), with the
22
+ README reorganized around it.
23
+ - The changelogs are in the package (`node_modules/domaintrail/CHANGELOG.md`, `CHANGELOG.ja.md`), and `dt guide` tells
24
+ agents to follow their Upgrading steps after an upgrade.
25
+
26
+ ## [0.5.0] - 2026-10-24
27
+
28
+ ### Added
29
+
30
+ - **Policies** (`dt guide reactions`, ADR 0022): `policy P when Event -> Aggregate.command` — when the event happens,
31
+ the command of the policy's own context is eventually run. M023 checks the event, the aggregate and the command, and
32
+ warns about the same reaction twice. Each policy generates the type of its handler, from the event to the command's
33
+ input (`<Command><Aggregate>Input`). With `generate.ts.policies: "required"` (default `"optional"`, also per context
34
+ in `generate.ts.contexts`), `<Context>PolicyHandlers` is generated and the contract tests take it and check that every
35
+ event resolves to an input the command accepts. The context map counts a policy as an event dependency.
36
+ - **Verified types** (`dt guide verified`, ADR 0023): `verified X of T for C? errors E` — the evidence of a check made
37
+ outside the aggregate (an email address nobody else uses). Only its async factory, `Make<Name>`, written by you, makes
38
+ it; commands take it as a parameter. M024 keeps verified types to command parameters.
39
+ - **Payload names** (ADR 0021): M022 warns when an event or error field likely takes its value from a differently
40
+ named source (a parameter, one of its fields, or a field of the aggregate before / after the command).
41
+ - **Zed extension** (`editors/zed`) and a **tree-sitter grammar** (`tree-sitter-domaintrail`): highlighting, outline and
42
+ `dt lsp` in Zed; the grammar for other tree-sitter editors (`dt guide editors`).
43
+ - **Linting against casts** (`dt guide lint`): how to make every domain type without `as`, and how to enable
44
+ `no-unsafe-type-assertion` with oxlint or typescript-eslint. `dt generate ts` prints a hint (`hints` in
45
+ `--format json`) when no oxlint or ESLint config mentions the rule; `"lintHint": false` turns it off. The JSDoc of
46
+ IDs, domain primitives and verified types names the way to make them.
47
+ - `parseDecimal(text)` in the generated runtime: the one place that makes a `Decimal`.
48
+ - `dt guide policies`: how to write rules that depend on a changeable policy.
49
+ - `examples/shop` is a complete project: every aggregate implemented, an application layer (use cases, repositories, an
50
+ event store, policies, a verified type's factory), tests and a demo, described in its README.
51
+ - Language server: commands can be renamed.
52
+
53
+ ### Changed
54
+
55
+ - Every command that collects the workspace's files (`check`, `generate`, `view`, `fmt`, `show`, `lsp`) skips what the
56
+ `.gitignore` files (of the root, its subdirectories and its parents up to the repository's top) and
57
+ `.git/info/exclude` ignore. `"respectGitignore": false` turns it off; files named on the command line are never
58
+ skipped.
59
+ - When the language server finds no config file, the first `.dt` file opened below a config makes that config the
60
+ workspace.
61
+
62
+ ### Fixed
63
+
64
+ - A domain primitive over `Int` or `Decimal` can be compared with the numbers `count()` and `length()` compute.
65
+
66
+ ### Upgrading
67
+
68
+ 1. If `.dt` files of your model are in a directory that `.gitignore` ignores, they are no longer part of the
69
+ workspace. Move them, or set `"respectGitignore": false` in `domaintrail.jsonc`. Run `dt check` and compare the
70
+ file count with before.
71
+ 2. M011: `parseDecimal` is now a runtime name. Rename an element of your model with that name.
72
+ 3. Run `dt generate ts`. If it prints the lint hint, follow `dt guide lint` (or set `"lintHint": false`), and replace
73
+ casts to domain types in your code with `make<Name>` / `parseDecimal`.
74
+ 4. New warnings may appear: M022 (rename the event or error field after its source, or the source after the field).
75
+ Warnings do not fail `dt check`.
76
+
77
+ ## [0.4.0] - 2026-10-24
78
+
79
+ ### Added
80
+
81
+ - **`requires`** on commands (`dt guide requires`, ADR 0017): `requires name { expr } else Error` — the command is
82
+ accepted only if the expression holds, otherwise it returns the error (M019).
83
+ - **`changes`** on commands (`dt guide changes`, ADR 0019): the fields a command may change (M021).
84
+ - **`emits` with multiplicities and `when`** (`dt guide events`, ADR 0018): `X?`, `X[0..*]`, `X[1..*]`, `X[n..m]`, and
85
+ `X when Draft ->` / `X when -> Completed` for events of some outcomes only.
86
+ - **`unique` and `contains`** in expressions, and invariants without an expression, stated in words only (M018; ADR
87
+ 0016).
88
+ - **Decisions and `decide`** (`dt guide decide`): each command is implemented as a `<Command><Aggregate>Decision` — per
89
+ source state, the outcome's `set` and `events` — and `decide(<Aggregate>Shape, decision)` from the runtime makes the
90
+ aggregate. The compiler names a missing event, an extra event, and a field the command may not set.
91
+ - **Generated checks and constructors** (ADR 0020): `check<Name>` / `check<Command><Aggregate>Requires` from the
92
+ invariant and requirement expressions, and `make<Name>` for every ID and domain primitive.
93
+ - **Generated contract tests** (`dt guide tests`): with `generate.ts.tests` (`runner`: `bun`, `vitest`, `jest` or
94
+ `node`; `out`), `dt generate ts` writes fast-check suites per context that check the constructors, the requirements,
95
+ the invariants after each command, the events and purity. They need `fast-check` in your project.
96
+ - **`dt fmt`**: one canonical layout for `.dt` files, keeping every comment (`--check` for CI); also LSP formatting.
97
+ - **`dt show <name>`**: one element, state, command or context resolved, with who uses it (`--format json`).
98
+ - **`dt lsp`**: a language server with the diagnostics of `dt check`, definition, references, hover, outline,
99
+ completion and rename.
100
+ - **Hints** on diagnostics (did you mean …, and what to change), and `--format json` for `check` and `generate ts`
101
+ (schema in `schema/diagnostics.schema.json`).
102
+ - `@see <file>:<line>` on every generated declaration, pointing back to the DSL.
103
+ - Runtime helpers `outcome(value)` (`{ to, aggregate, events }`) and `eventList(events)` for code that handles any
104
+ command.
105
+ - M020 (warning): a `create command` without a parameter of the aggregate's ID type.
106
+
107
+ ### Changed
108
+
109
+ - **`emits X` means exactly one `X`** when the command succeeds (it meant "may emit"), and nothing on an error.
110
+ - **Command results are keyed by outcome**: `{ Confirmed: { aggregate, events: { OrderConfirmed } } }` instead of the
111
+ tuple `[aggregate, events[]]`; events are keyed by name and have no order. A command without events has `NoEvents`.
112
+ - Commands are pure: IDs and times come in as parameters (factories take their ID).
113
+ - `ok` infers its type from the contract only, so an extra event in the literal is reported.
114
+ - Without `changes`, a command changes no common field.
115
+ - Generated modules export values (`<Aggregate>Shape`): import them as values, not with `import type`.
116
+ - `dt guide` prints a quick reference and the list of topics; `dt guide <topic>` one section, `dt guide <rule>` one
117
+ rule, `dt guide --all` the whole guide.
118
+ - `[0..1]` is the same as `?` and `[1..1]` the same as no multiplicity.
119
+ - The Live View shows the multiplicity and `when` of each event, `requires` and `changes`, and hints in the problems
120
+ pane.
121
+
122
+ ### Removed
123
+
124
+ - `byState` and the `<Command><Aggregate>ByState` types (0.3.0): every command is implemented with `decide`.
125
+
126
+ ### Upgrading
127
+
128
+ Change the model first, then generate, then the code. The full rules are in `dt guide events`, `dt guide changes` and
129
+ `dt guide decide`.
130
+
131
+ 1. **Events.** `emits X` is now exactly one `X` on every success. For each `emits` entry, decide when the event
132
+ happens and write it so:
133
+
134
+ ```dt
135
+ // before (0.3.0): "may emit"
136
+ command changeNote(note: String?)
137
+ Draft -> Draft, Confirmed -> Confirmed
138
+ emits NoteChanged
139
+ command cancel(reason: CancelReason)
140
+ Draft | Confirmed -> Cancelled
141
+ emits OrderCancelled, RefundRequested
142
+ // after (0.4.0)
143
+ command changeNote(note: String?)
144
+ Draft -> Draft, Confirmed -> Confirmed
145
+ changes note
146
+ emits NoteChanged? // at most one; X[0..*] for any number
147
+ command cancel(reason: CancelReason)
148
+ Draft | Confirmed -> Cancelled
149
+ emits OrderCancelled,
150
+ RefundRequested when Confirmed -> // only from Confirmed
151
+ ```
152
+
153
+ 2. **Factories take their ID, commands take the time.** Add a parameter of the aggregate's ID type to each
154
+ `create command` (M020 warns until you do), and a `Timestamp` parameter where the implementation read the clock:
155
+
156
+ ```dt
157
+ create command place(customer: CustomerId) -> Draft // before
158
+ create command place(id: OrderId, customer: CustomerId) -> Draft // after
159
+ ```
160
+
161
+ Callers make the ID (`make<Name>(crypto.randomUUID())`) and pass it in.
162
+ 3. **`changes`.** Add `changes f1, f2` to every command that changes a field common to its source and target states
163
+ (or any field of an aggregate without states). Without it, the generated decision does not let the command set the
164
+ field.
165
+ 4. **Optionally `requires`.** A check that returns an error before anything changes can become
166
+ `requires name { expr } else Error`; the generated `check<Command><Aggregate>Requires` and the contract tests then
167
+ check it.
168
+ 5. **Reserved names (M011).** `decide`, `Decide`, `Shape`, `NoChanges`, `outcome`, `eventList`, `NoEvents`, `Outcome`,
169
+ `EventOf`, `sameOptional`, `sameList` and `distinct` are runtime names, and `<Command><Aggregate>Decision`,
170
+ `<Aggregate>Shape` are generated: rename elements whose names clash. Run `dt check` until it exits 0.
171
+ 6. **Regenerate and rewrite every command with `decide`.** Return the outcome's `set` (the fields in `changes` and the
172
+ target state's new fields; a factory sets every field, optional ones as `undefined`) and its `events`, keyed by the
173
+ target state (by the aggregate's name without states). One handler per source state, also with one source; a
174
+ factory is a single function:
175
+
176
+ ```ts
177
+ // before (0.3.0)
178
+ import { byState, ok, type Sales } from '../generated/domain/index.js';
179
+ export const orderCommands: Sales.OrderCommands = {
180
+ place: (customer) => {
181
+ const order: Sales.DraftOrder = { state: 'Draft', id: crypto.randomUUID() as Sales.OrderId, items: [], customer };
182
+ return ok([order, [{ type: 'OrderPlaced', orderId: order.id }]] as const);
183
+ },
184
+ confirm: (order, at) =>
185
+ ok([{ ...order, state: 'Confirmed', confirmedAt: at }, [{ type: 'OrderConfirmed', orderId: order.id, confirmedAt: at }]] as const),
186
+ changeNote: byState<Sales.ChangeNoteOrderByState>({
187
+ Draft: (order, note) => ok([{ ...order, note }, []] as const),
188
+ Confirmed: (order, note) => ok([{ ...order, note }, []] as const),
189
+ }),
190
+ };
191
+
192
+ // after (0.4.0)
193
+ import { Sales, decide, ok } from '../generated/domain/index.js';
194
+ export const orderCommands: Sales.OrderCommands = {
195
+ place: decide<Sales.PlaceOrderDecision>(Sales.OrderShape, (id, customer) =>
196
+ ok({ Draft: { set: { id, items: [], customer, note: undefined }, events: { OrderPlaced: { type: 'OrderPlaced', orderId: id } } } }),
197
+ ),
198
+ confirm: decide<Sales.ConfirmOrderDecision>(Sales.OrderShape, {
199
+ Draft: (order, at) =>
200
+ ok({ Confirmed: { set: { confirmedAt: at }, events: { OrderConfirmed: { type: 'OrderConfirmed', orderId: order.id, confirmedAt: at } } } }),
201
+ }),
202
+ changeNote: decide<Sales.ChangeNoteOrderDecision>(Sales.OrderShape, {
203
+ Draft: (order, note) => ok({ Draft: { set: { note }, events: {} } }), // with `emits NoteChanged?`, give it when the note changes
204
+ Confirmed: (order, note) => ok({ Confirmed: { set: { note }, events: {} } }),
205
+ }),
206
+ };
207
+ ```
208
+
209
+ 7. **Callers** read the keyed result: `result.value.Confirmed.aggregate` / `.events.OrderConfirmed`, or, in code that
210
+ handles any command, `outcome(result.value)` → `{ to, aggregate, events }` and `eventList(events)`.
211
+ 8. Type-check (`tsc --noEmit`). When the contract and the implementation disagree — an event that cannot always be
212
+ emitted, a field that must change — change the DSL (`X?`, `changes`), not the implementation.
213
+ 9. Optionally enable the contract tests (`generate.ts.tests`, `dt guide tests`) and run `dt fmt` once.
214
+
215
+ ## [0.3.0] - 2026-10-19
216
+
217
+ ### Added
218
+
219
+ - Handlers per source state: a command whose targets depend on its source state also gets
220
+ `<Command><Aggregate>ByState`, implemented with `byState({...})` from the runtime (ADR 0015).
221
+ - The State View draws one edge per transition, and the inspector shows a row per transition.
222
+
223
+ ### Changed
224
+
225
+ - **Commands list their transitions** instead of `from … to …` (ADR 0014): `Draft -> Confirmed`,
226
+ `Draft | Confirmed -> Cancelled`, `Draft -> Draft, Confirmed -> Confirmed`; a factory has one transition without
227
+ sources, `-> Draft`. A state is the source of one transition per command (M006). `from` and `to` are no longer
228
+ keywords.
229
+ - The generated contract of a command whose targets depend on its source state is generic over the input state, so a
230
+ `Draft` gives a `Draft` and a `Confirmed` a `Confirmed`.
231
+
232
+ ### Fixed
233
+
234
+ - The `bin` paths of the package are written so that `npm publish` does not warn about them.
235
+
236
+ ### Upgrading
237
+
238
+ 1. Rewrite each command's `from … to …` as transitions:
239
+
240
+ ```dt
241
+ // before (0.2.0)
242
+ create command place(customer: CustomerId) to Draft
243
+ command confirm(at: Timestamp) from Draft to Confirmed emits OrderConfirmed
244
+ command changeNote(note: String?) from Draft | Confirmed to Draft | Confirmed
245
+ // after (0.3.0)
246
+ create command place(customer: CustomerId) -> Draft
247
+ command confirm(at: Timestamp) Draft -> Confirmed emits OrderConfirmed
248
+ command changeNote(note: String?) Draft -> Draft, Confirmed -> Confirmed // say which source goes where
249
+ ```
250
+
251
+ `from A | B to C | D` becomes `A | B -> C | D` when every source may end in every target; prefer the precise
252
+ transitions.
253
+ 2. Run `dt check`, then `dt generate ts`. A command whose targets depend on its source state has a generic contract:
254
+ implement it with `byState<Ctx.<Command><Aggregate>ByState>({ Draft: (…) => …, Confirmed: (…) => … })` instead of
255
+ a type assertion.
256
+
257
+ ## [0.2.0] - 2026-10-18
258
+
259
+ ### Added
260
+
261
+ - **Field sets** (`dt guide field-sets`, ADR 0013): `fields Name { ... }` declares plain fields that aggregates,
262
+ entities and events take on with `with A, B` (M016). Shared contexts may hold them. They generate an interface that
263
+ the elements extend.
264
+ - Generated unions per context: `DomainEvent` (every event), `Aggregate` (every aggregate) and `<Aggregate>Event` (the
265
+ events an aggregate's commands emit).
266
+ - M017 (warning): an event emitted by an aggregate's command has no field of that aggregate's ID type.
267
+ - The guide explains domain vs technical fields, and attaching technical metadata with envelopes.
268
+ - Live View: a filter bar in the catalog (text, kinds, contexts); field sets in the explorer, catalog, inspector and
269
+ structure diagram.
270
+
271
+ ### Changed
272
+
273
+ - Live View: catalog cards put the badges above the heading and the label over the name; the catalog's section links
274
+ are gone (the filter finds a section).
275
+
276
+ ### Upgrading
277
+
278
+ 1. M011: `DomainEvent`, `Aggregate` and `<Aggregate>Event` are generated names now. Rename an element of a context
279
+ whose name clashes with them, then run `dt check`.
280
+ 2. `fields` and `with` are keywords; they are still allowed as field and parameter names.
281
+ 3. Address the new M017 warnings by adding the aggregate's ID to its events (`orderId: OrderId`).
282
+
283
+ ## [0.1.0] - 2026-10-18
284
+
285
+ The first published version.
286
+
287
+ ### Added
288
+
289
+ - The DomainTrail DSL: bounded contexts, aggregates with states, entities, value objects, IDs, domain primitives,
290
+ enums, unions, commands (`from … to …`), events (`persistent`), errors, shared contexts, labels and descriptions,
291
+ and invariant expressions; rules M001–M015 and L001–L003.
292
+ - The `dt` CLI (also installed as `domaintrail`), on Node.js 22.12 or later: `dt check`, `dt generate ts` (TypeScript
293
+ types and command contracts, one output directory per context with `generate.ts.contexts`), `dt view` and
294
+ `dt guide`.
295
+ - `domaintrail.jsonc` (`sources`, `generate.ts`, `view`) with its JSON Schema in `schema/domaintrail.schema.json`.
296
+ - The Live View (`dt view`): the context map, the structure diagram (three levels), the catalog, the state diagram,
297
+ the inspector with the source, problems and changes, updated on every save; in Japanese or English, light or dark.
298
+
299
+ [0.6.0]: https://github.com/hisamekms/domaintrail/compare/v0.5.0...v0.6.0
300
+ [0.5.0]: https://github.com/hisamekms/domaintrail/compare/v0.4.0...v0.5.0
301
+ [0.4.0]: https://github.com/hisamekms/domaintrail/compare/v0.3.0...v0.4.0
302
+ [0.3.0]: https://github.com/hisamekms/domaintrail/compare/v0.2.0...v0.3.0
303
+ [0.2.0]: https://github.com/hisamekms/domaintrail/compare/b9f56885cde82f404bbf6b558f43b8ecb6254dfe...v0.2.0
304
+ [0.1.0]: https://github.com/hisamekms/domaintrail/tree/b9f56885cde82f404bbf6b558f43b8ecb6254dfe