litedbmodel 2.2.2 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +2 -1
  2. package/dist/DBModel.d.ts.map +1 -1
  3. package/dist/DBModel.js +3 -0
  4. package/dist/DBModel.js.map +1 -1
  5. package/dist/Middleware.d.ts +19 -2
  6. package/dist/Middleware.d.ts.map +1 -1
  7. package/dist/Middleware.js +1 -0
  8. package/dist/Middleware.js.map +1 -1
  9. package/dist/decorators.d.ts.map +1 -1
  10. package/dist/decorators.js +8 -0
  11. package/dist/decorators.js.map +1 -1
  12. package/dist/drivers/MysqlHelper.d.ts +1 -1
  13. package/dist/drivers/MysqlHelper.d.ts.map +1 -1
  14. package/dist/drivers/MysqlHelper.js.map +1 -1
  15. package/dist/drivers/PostgresHelper.d.ts +1 -1
  16. package/dist/drivers/PostgresHelper.d.ts.map +1 -1
  17. package/dist/drivers/PostgresHelper.js.map +1 -1
  18. package/dist/drivers/SqliteHelper.d.ts +1 -1
  19. package/dist/drivers/SqliteHelper.d.ts.map +1 -1
  20. package/dist/drivers/SqliteHelper.js.map +1 -1
  21. package/dist/index.cjs +99 -33
  22. package/dist/scp/connection-routing.d.ts +38 -1
  23. package/dist/scp/connection-routing.d.ts.map +1 -1
  24. package/dist/scp/connection-routing.js +64 -5
  25. package/dist/scp/connection-routing.js.map +1 -1
  26. package/dist/scp/dbmodel-runtime.d.ts +2 -1
  27. package/dist/scp/dbmodel-runtime.d.ts.map +1 -1
  28. package/dist/scp/dbmodel-runtime.js +2 -1
  29. package/dist/scp/dbmodel-runtime.js.map +1 -1
  30. package/dist/scp/decorator-adapter.d.ts +22 -0
  31. package/dist/scp/decorator-adapter.d.ts.map +1 -1
  32. package/dist/scp/decorator-adapter.js +26 -0
  33. package/dist/scp/decorator-adapter.js.map +1 -1
  34. package/dist/scp/emit/emitter.d.ts +21 -10
  35. package/dist/scp/emit/emitter.d.ts.map +1 -1
  36. package/dist/scp/emit/emitter.js +119 -51
  37. package/dist/scp/emit/emitter.js.map +1 -1
  38. package/dist/scp/emit/endpoint.d.ts +3 -8
  39. package/dist/scp/emit/endpoint.d.ts.map +1 -1
  40. package/dist/scp/exec-context.d.ts +39 -17
  41. package/dist/scp/exec-context.d.ts.map +1 -1
  42. package/dist/scp/exec-context.js +69 -35
  43. package/dist/scp/exec-context.js.map +1 -1
  44. package/dist/scp/grouping.d.ts.map +1 -1
  45. package/dist/scp/grouping.js +0 -0
  46. package/dist/scp/grouping.js.map +1 -1
  47. package/dist/scp/index.cjs +251 -629
  48. package/dist/scp/index.d.ts +6 -11
  49. package/dist/scp/index.d.ts.map +1 -1
  50. package/dist/scp/index.js +10 -28
  51. package/dist/scp/index.js.map +1 -1
  52. package/dist/scp/index.mjs +251 -617
  53. package/dist/scp/leaf-transport.d.ts +127 -39
  54. package/dist/scp/leaf-transport.d.ts.map +1 -1
  55. package/dist/scp/leaf-transport.js +30 -24
  56. package/dist/scp/leaf-transport.js.map +1 -1
  57. package/dist/scp/leaves.d.ts +57 -20
  58. package/dist/scp/leaves.d.ts.map +1 -1
  59. package/dist/scp/leaves.js +224 -65
  60. package/dist/scp/leaves.js.map +1 -1
  61. package/dist/scp/makesql/compile-select.d.ts +28 -1
  62. package/dist/scp/makesql/compile-select.d.ts.map +1 -1
  63. package/dist/scp/makesql/compile-select.js +19 -17
  64. package/dist/scp/makesql/compile-select.js.map +1 -1
  65. package/dist/scp/makesql/index.d.ts +2 -2
  66. package/dist/scp/makesql/index.d.ts.map +1 -1
  67. package/dist/scp/makesql/index.js +4 -6
  68. package/dist/scp/makesql/index.js.map +1 -1
  69. package/dist/scp/makesql/tx.d.ts +27 -69
  70. package/dist/scp/makesql/tx.d.ts.map +1 -1
  71. package/dist/scp/makesql/tx.js +26 -448
  72. package/dist/scp/makesql/tx.js.map +1 -1
  73. package/dist/scp/middleware.d.ts +4 -4
  74. package/dist/scp/middleware.d.ts.map +1 -1
  75. package/dist/scp/middleware.js +2 -2
  76. package/dist/scp/middleware.js.map +1 -1
  77. package/dist/scp/relation.d.ts +31 -12
  78. package/dist/scp/relation.d.ts.map +1 -1
  79. package/dist/scp/relation.js +16 -6
  80. package/dist/scp/relation.js.map +1 -1
  81. package/dist/scp/typed-object.d.ts +23 -19
  82. package/dist/scp/typed-object.d.ts.map +1 -1
  83. package/dist/scp/typed-object.js +24 -30
  84. package/dist/scp/typed-object.js.map +1 -1
  85. package/dist/scp/write-bundle.d.ts +21 -70
  86. package/dist/scp/write-bundle.d.ts.map +1 -1
  87. package/dist/scp/write-bundle.js +15 -79
  88. package/dist/scp/write-bundle.js.map +1 -1
  89. package/dist/types.d.ts +12 -0
  90. package/dist/types.d.ts.map +1 -1
  91. package/dist/types.js.map +1 -1
  92. package/docs/architecture.md +452 -0
  93. package/docs/design/phase-a-execution-context.md +2 -0
  94. package/package.json +14 -3
  95. package/dist/scp/writes.d.ts +0 -251
  96. package/dist/scp/writes.d.ts.map +0 -1
  97. package/dist/scp/writes.js +0 -176
  98. package/dist/scp/writes.js.map +0 -1
  99. package/docs/proposal/contracts-architecture-feasibility.md +0 -328
  100. package/docs/proposal/contracts-architecture.md +0 -579
  101. package/docs/proposal/litedbmodel-v2-scp-architecture.md +0 -433
  102. package/docs/proposal/sql-dynamic-expansion-spec.md +0 -175
  103. package/docs/proposal/v0-coverage-matrix.md +0 -157
  104. package/docs/proposal/v2-completeness-plan.md +0 -90
  105. package/docs/proposal/v2-sql-parity-checklist.md +0 -162
@@ -0,0 +1,452 @@
1
+ # litedbmodel v2 アーキテクチャ仕様 — SCP / 多言語 CQRS
2
+
3
+ litedbmodel v2 は **behavior-contracts(bc)の汎用 SCP レイヤを consume する SQL バックエンド** である。
4
+ graphddb(DynamoDB バックエンド)と同型で、差分は **SQL 方言のコンパイルと実行の層だけ**。本書はその
5
+ 確定アーキテクチャの正式仕様であり、記述はすべて `07679b2` 時点の実装に対して裏取りされている(節ごとに
6
+ `` `symbol` (`file`) `` の形でコードを引用する)。コードが SoT であり、本書が食い違ったらコードを正とする。
7
+
8
+ 対象言語は **TypeScript + Rust + Python + Go + PHP の5言語 parity**。方言は **PostgreSQL / MySQL / SQLite**。
9
+
10
+ > **本書の節番号はコードから参照される。** `src/scp/**` の複数ファイルが `spec §1` / `spec §4.1` /
11
+ > `spec §5` / `spec §6` / `spec §8` / `spec §10` / `spec §11` / `spec §13` を doc-comment とエラーメッセージ
12
+ > で引用している(`` `scripts/check-spec-refs.mjs` `` がその整合を機械検証する — §10)。節を renumber しない。
13
+
14
+ ---
15
+
16
+ ## 0. 要約(TL;DR)
17
+
18
+ - litedbmodel v2 は **新しい DSL を作らない**。bc の Behavior / Component / Port / Wire + Expression IR +
19
+ runtime-core を consume する **SQL バックエンド consumer** になる。
20
+ - **コンパイル経路は1本**(§9)。ORM ユーザはデコレータでモデルと **宣言エンドポイント**(SQL を書かない)
21
+ を書き、`` `emitBehaviorModule` (`src/scp/emit/emitter.ts`) `` が **SCP 制限 TS**(`@behavior static`
22
+ メソッドが唯一のリーフカタログ `Db` を呼ぶ形)へ lowering し、`bc generate --from` がそれを読んで
23
+ go / rust / py / php / ts の native モジュールを生成する。リーフへの配線は bc が自動生成する。
24
+ - **実行時のサーフェスは3本の op 非依存リーフだけ**: `` `Db.executeSQL` / `Db.pluck` / `Db.group`
25
+ (`src/scp/leaf-transport.ts`) ``。方言 SQL はこの3本が運ぶテキスト+パラメータに閉じる。
26
+ - 形が実行時にしか決まらない **真のアドホッククエリ**(`find({ where })`)は静的 SQL を持たないので、
27
+ codegen 経路ではなく **v1 命令パス**で走る(§9)。
28
+ - litedbmodel は **可搬 IR ドキュメントを emit しない**(§8)。方言は TS 側で一度だけ解決され、方言別の
29
+ SQL が emit 時に SCP 制限 TS へ焼き込まれる。
30
+ - **多言語 conformance** は「方言 3 × 言語 5」の格子を **3 + 5** に分解する(§10)。
31
+ - v2.0 系は破壊的変更。v1.x はメンテブランチ `v1.x` で保全。
32
+
33
+ ---
34
+
35
+ ## 1. 位置づけ — litedbmodel は SCP の SQL バックエンド consumer
36
+
37
+ behavior-contracts は SCP を **汎用化**し、consumer が差し込む拡張点を確定させている。litedbmodel v2 は
38
+ graphddb と並ぶ SQL backend の consumer である。`` `src/scp/index.ts` `` の先頭が公開サーフェスを4点に
39
+ 確定している(`litedbmodel supplies exactly four things and nothing else`):
40
+
41
+ 1. **リーフ転送宣言** — `Db`(`@leaf static executeSQL / pluck / group`)。`bc generate --from` が読む、
42
+ リポジトリで唯一のリーフカタログ(`src/scp/leaf-transport.ts`)。
43
+ 2. **リーフ転送実装** — `` `leafHandlers` / `leafHandlersAsync` (`src/scp/leaves.ts`) ``。bc 生成 TS
44
+ モジュールの `bind(handlers)` / `bindAsync(handlers)` が consume する TS 実行シーム。
45
+ 3. **チューニング済み SQL** — `src/scp/makesql` サブツリー(方言別 SELECT / INSERT / UPDATE / DELETE /
46
+ relation / batch ビルダ、v1 ビルダと byte 一致)と、その上の write bundle / transaction plan。
47
+ 4. **各言語ランタイム** — 実行コンテキスト(接続ルーティング・middleware・transaction)、relation grouping、
48
+ 列型 de-box の SoT、typed-object 読み出しサーフェス。
49
+
50
+ **配線は自動**。behavior は TS で宣言され bc がコンパイルし、言語別の手配線は「ハーネスが生成メソッドを
51
+ リーフハンドラ付きで呼ぶ」部分だけ。**ランタイム IR も、プログラム的コンパイルも、ランタイム behavior
52
+ ディスパッチも、このパッケージには存在しない**(`src/scp/index.ts`)。
53
+
54
+ **litedbmodel v2 が実装する拡張点(これ以外は bc から得る):**
55
+
56
+ | 拡張点 | litedbmodel v2 での中身 |
57
+ |---|---|
58
+ | **Catalog leaf** | `makeSQL(sql, params, skip?)`(bc catalog leaf。`src/scp/makesql/makesql.ts`)と、codegen 経路の3本 op 非依存リーフ `Db`(§8/§11) |
59
+ | **Authoring** | デコレータ付きモデル + 宣言エンドポイント(`src/scp/emit/endpoint.ts`)→ SCP 制限 TS |
60
+ | **Backend Compile** | 宣言エンドポイント → 方言 SQL テキスト + `?` パラメータ(`src/scp/makesql`)。`?`→`$N` は runtime 最終1パス |
61
+ | **Handler** | catalog leaf → driver 実行 + 行→論理モデル assembly(`src/scp/makesql/handler.ts`) |
62
+ | **Error Mapping** | driver エラー → SCP Failure(`src/scp/errors.ts`) |
63
+
64
+ ---
65
+
66
+ ## 2. 利用モード(authoring サーフェス)
67
+
68
+ ### 2.1 モデル定義(デコレータ)
69
+
70
+ `@model(table)` / `@column(...)` で物理対応を宣言する(v1 継承)。結果は **typed-object**(DBModel
71
+ インスタンスではない・§4 / §12)。列型は SQL 型を SoT とする(§4.1)。
72
+
73
+ ```ts
74
+ @model('posts')
75
+ export class PostModel extends DBModel {
76
+ @column({ primaryKey: true }) id!: number;
77
+ @column() author_id!: number;
78
+ @column() title!: string;
79
+
80
+ @belongsTo(() => [Post.author_id, User.id]) declare author: User | null;
81
+ }
82
+ export const Post = PostModel.asModel();
83
+ ```
84
+
85
+ ### 2.2 宣言エンドポイント(抽象 API・SQL を書かない)
86
+
87
+ 静的に形が決まるクエリは **宣言エンドポイント**として書く。エンドポイントの語彙は
88
+ `` `Endpoint` (`src/scp/emit/endpoint.ts`) `` が確定させており、**SQL を一切含まない**: 述語はモデルの
89
+ **列**と**パラメータ**を名指し、relation は `@hasMany`/`@belongsTo`/`@hasOne` プロパティを名指し、書込は
90
+ 束ねる列を名指す。方言 SQL は emitter が `makesql` ビルダから生成し、パラメータ型はモデルの `@column`
91
+ メタから解決する(手書きしない)。
92
+
93
+ 宣言できるエンドポイント種別は **7つに閉じている**(`Endpoint` union):
94
+ `read` / `create` / `update` / `delete` / `createMany` / `updateMany` / `deleteMany`。
95
+
96
+ <!--@embedoc:code_snippet file="src/scp/emit/endpoint.ts" block_start="export type Endpoint =" block_end="DeleteManyEndpoint;"-->
97
+ ```ts
98
+ export type Endpoint =
99
+ | ReadEndpoint
100
+ | CreateEndpoint
101
+ | UpdateEndpoint
102
+ | DeleteEndpoint
103
+ | CreateManyEndpoint
104
+ | UpdateManyEndpoint
105
+ | DeleteManyEndpoint;
106
+ ```
107
+ <!--@embedoc:end-->
108
+
109
+ 述語(`Predicate` union)は列 op パラメータ比較(`ComparePredicate`)、IN リスト(`InPredicate`)、
110
+ 複合キー IN(`TupleInPredicate`)、NULL 判定(`NullPredicate`)、相関 EXISTS(`ExistsPredicate`)、
111
+ 型付きサブクエリ(`SubqueryPredicate`)。`ComparePredicate.optional` を立てた述語だけが **SKIP メンバー**
112
+ になる(§8)。
113
+
114
+ ```ts
115
+ // 宣言エンドポイントの集合 = 1つの emit された @behavior クラス(EndpointSet)
116
+ const PostQueries: EndpointSet = {
117
+ search: {
118
+ kind: 'read',
119
+ model: Post,
120
+ where: [
121
+ { column: 'author_id', op: 'eq', param: 'authorId' },
122
+ { column: 'status', op: 'eq', param: 'status', optional: true }, // optional → SKIP メンバー
123
+ { column: 'created_at', op: 'ge', param: 'since' },
124
+ ],
125
+ order: 'created_at DESC',
126
+ limit: { param: 'limit' },
127
+ with: ['author'], // relation は宣言 select(batch, N+1-free)
128
+ },
129
+ createPost: {
130
+ kind: 'create',
131
+ model: Post,
132
+ values: [{ column: 'author_id', param: 'authorId' }, { column: 'title', param: 'title' }],
133
+ returning: ['id', 'title'],
134
+ },
135
+ };
136
+ ```
137
+
138
+ ### 2.3 TS 直接利用(公開 API・eager)
139
+
140
+ 従来どおり公開 API を直接呼ぶ。静的に宣言したエンドポイントは codegen 経路(§9)に載るが、形が実行時に
141
+ しか決まらない **真のアドホッククエリ**(`find({ where: { age: { gt: x } } })`)は静的 SQL を持たないので
142
+ **v1 命令パス**で走る(`Endpoint` の doc-comment: `an ad-hoc find({ where }) has no static SQL and
143
+ runs the v1 imperative path`)。複数文をまたぐ書込の原子性は利用者が `DBModel.transaction(fn)` で明示的に
144
+ 束ねる(§6)。
145
+
146
+ ### 2.4 多言語利用(生成物を各言語 runtime で)
147
+
148
+ `bc generate` が publish した native モジュールを、各言語の薄い runtime(bc runtime-core + litedbmodel
149
+ SQL runtime)が読み、**同一 SQL・同一結果**を実行する。生成エントリは authored メソッドの 1:1 写像で、
150
+ 宣言クラスが名前空間・引数がその順の位置引数(シグネチャ直呼び):
151
+
152
+ ```go
153
+ rows, _ := postQueries.Search(ctx, db, SearchInput{AuthorID: 7, Since: "2026-01-01"})
154
+ ```
155
+
156
+ ---
157
+
158
+ ## 3. レイヤ構成
159
+
160
+ ```
161
+ デコレータ付きモデル + 宣言エンドポイント(SQL なし) ← src/decorators.ts + src/scp/emit/endpoint.ts
162
+ ↓ emitBehaviorModule(lowering) ← src/scp/emit/emitter.ts
163
+ SCP 制限 TS: @behavior static メソッド(方言 SQL は makesql で焼き込み済み)が唯一の @leaf カタログ Db を呼ぶ
164
+ ↓ bc generate --from
165
+ go / rust / py / php / ts native モジュール(リーフ配線は bc が自動生成)
166
+ ↓ 実行時
167
+ 薄い Runtime: 3本の op 非依存リーフ(executeSQL / pluck / group)— §8/§9
168
+ ```
169
+
170
+ **汎用(behavior-contracts)と SQL backend(litedbmodel)の分界:**
171
+
172
+ | 汎用(behavior-contracts) | SQL backend(litedbmodel v2) |
173
+ |---|---|
174
+ | Component-graph IR 構造 / Execution Plan / Expression IR 評価 | 方言 SQL の Backend Compile(`src/scp/makesql`) |
175
+ | `runBehavior` / `evaluateExpression` / `validateEnvelope` / codegen 基盤 | catalog leaf handler + 行→論理モデル assembly(`src/scp/makesql/handler.ts`) |
176
+ | conformance runner 基盤(言語軸) | 方言別 SQL 生成(§13 の closeable convention 含む)・型 hydration・接続/プール/tx |
177
+
178
+ ---
179
+
180
+ ## 4. モデル定義
181
+
182
+ - `@model(table)` / `@column(...)` で物理対応(v1 継承)。読み出し結果は **typed-object**(own props は
183
+ データのみ・DBModel インスタンスではない。`src/scp/typed-object.ts`)。
184
+ - 論理モデル ↔ 物理配置(table/column/PK/index)はモデル定義が吸収する。
185
+
186
+ ### 4.1 型システム(SQL 型ベース・SSoT)
187
+
188
+ litedbmodel は SQL バックエンド consumer なので **列型は SQL 型を SoT とする**(TS の `number` は
189
+ INTEGER/REAL を区別できないため型の権威にしない)。型は `schema.sql`(DDL)から確定し、typed codegen
190
+ (bc typed-raw 脱 box)の `outType` 注記に使う。**曖昧/未知は error(no-assume・no-fallback)** — 既定値へ
191
+ 潰さない。変換は `` `sqlTypeToBcScalar` (`src/scp/coltype.ts`) `` が SSoT。
192
+
193
+ **SQL 型 → bc outType スカラ(正規対応表・`sqlTypeToBcScalar`)**
194
+
195
+ | SQL 型 | bc outType | 備考 |
196
+ |---|---|---|
197
+ | INTEGER / INT / BIGINT / SMALLINT / TINYINT / SERIAL 系 | `int` | int は既定 **64bit**。狭いサイズ制限は列制約で表現し別型にしない |
198
+ | REAL / FLOAT / DOUBLE | `float` | int と real は明確に分離 |
199
+ | DECIMAL / NUMERIC / MONEY | `string` | 精度保持のため文字列表現 |
200
+ | TEXT / VARCHAR / CHAR / CHARACTER (VARYING) / CLOB / UUID | `string` | |
201
+ | BOOLEAN / BOOL | `bool` | |
202
+ | DATE / TIMESTAMP / TIMESTAMPTZ / DATETIME / TIME | `string` | 一旦 string(bc の date scalar は未導入)。DB は行ごとに別 TZ を保存しない |
203
+ | JSON / JSONB | `string` | 表現は JSON テキスト=文字列 |
204
+
205
+ nullability は基底スカラと直交する(`` `sqlTypeIsNotNull` (`src/scp/coltype.ts`) ``): `NOT NULL` 列は
206
+ 読みセルが非 null 確定なので `opt` ラップ無しの生スカラ、既定は nullable。
207
+
208
+ **TS 読み出しの materialization(`sqlTypeToMaterializeClass` / `materializeCell`)**
209
+
210
+ bc outType(可搬型)に加え、TS/driver の読み経路では `int` スカラを SQL 幅で分割し、`date`/`bool` を
211
+ 正確な JS 形へ矯正する(JS `number` は i64 を保持できず、driver は `string` outType に反する `Date`/`0|1`
212
+ を返し得るため)。クラスは `` `MaterializeClass` (`src/scp/coltype.ts`) ``:
213
+
214
+ - `int32`(INT/INTEGER/SMALLINT/…)→ JS `number`(範囲が収まる)。
215
+ - `int64`(BIGINT/INT8/BIGSERIAL)→ **値保存の10進文字列**(JS number は 2^53 超で丸む・JSON 安全)。
216
+ - `date`(DATE/TIMESTAMP/…)→ **TZ 付き文字列**(`string` outType に整合。全言語同一、TS も Date にしない)。
217
+ - `bool` → JS `boolean`。
218
+ - `passthrough`(float / text / decimal→string / json / uuid / 配列列)→ 無変換。
219
+
220
+ 読み出しは fail-closed の resolver(`` `failClosedMaterializeResolverFromColumnMap` (`src/scp/coltype.ts`) ``)
221
+ で常時 de-box され、宣言されていない列は throw する(未型付き列を silent に box しない)。
222
+
223
+ **脱 box は READ / WRITE 両方を覆う(codegen 面・§9)**
224
+
225
+ - **READ**: SELECT projection を行 obj 型へ型付け(`` `deriveReadRow` (`src/scp/makesql/outtype.ts`) ``)→
226
+ bc typed-raw が具体的な行 struct を materialize。
227
+ - **WRITE**: 書込出力=`TransactionResult` の typed shape(`` `deriveWriteOutputType`
228
+ (`src/scp/makesql/writeouttype.ts`) ``):
229
+ `obj{ committed:bool, executed:arr<string>, shortCircuit:opt<obj{statementId:string, reason:string}>,
230
+ entity:opt<ROW>, returnedRows:opt<arr<arr<ROW>>> }`。ROW は書込対象テーブルの RETURNING 列を **READ と
231
+ 同一の列型 resolver**で型付けする。脱 box できない write shape は error(no-assume・no-fallback)。
232
+
233
+ **規律**: 型が曖昧/未指定なら error。SoT は `schema.sql` の SQL 型(`` `parseSchemaColumnTypes`
234
+ (`src/scp/coltype.ts`) `` が DDL を `table → (column → SQL 型)` へパース)。
235
+
236
+ ---
237
+
238
+ ## 5. Relation — Read 系
239
+
240
+ Relation は **SQL JOIN を既定にしない**(`src/scp/relation.ts` の doc-comment: `Relations are NOT SQL
241
+ JOINs by default`)。v1 `LazyRelation` と同型の **staged batch query-composition + object assembly**:
242
+ 結果ページの親キー集合を集め、dedup した親キーで **ONE batched child SELECT** を撃ち、子行を親へ分配する
243
+ (relation エッジごとに1クエリ・N+1 なし)。
244
+
245
+ - `` `RelationOp` (`src/scp/relation.ts`) `` はモデル `RelationDecl` から **一度だけ**静的 `makeSQL` batch
246
+ SELECT へコンパイルされる(`` `compileRelationOp` (`src/scp/relation.ts`) ``)。PG は元 `LazyRelation` SQL
247
+ と byte 一致(`= ANY(?::type[])`, `CROSS JOIN LATERAL`, `UNNEST`)、MySQL/SQLite は単一 JSON パラメータの
248
+ サーバ側展開(`json_each` / `JSON_TABLE`)。dedup 済みキー配列は **ONE param・静的テキスト**で束ねる
249
+ (プレースホルダ件数展開なし)ので `op.sql` は値非依存で固定。
250
+ - **2つの読みサーフェスが同一 op を撃つ**(`` `runRelationOp` (`src/scp/relation.ts`) ``):
251
+ - **宣言 select**(`with: ['author']`)… ページ全体を batch 先読み。
252
+ - **lazy**(`await post.author`)… prototype getter が兄弟集合に同一 op を撃つ
253
+ (`src/scp/typed-object.ts` の非列挙 Symbol batch context)。
254
+ - **暴走ガード**: guarded relation の子フェッチは runaway cap(`` `CapGuard` (`src/scp/leaf-transport.ts`) ``)
255
+ を運び、リーフが生の子行件数を cap に照合して超過時 `LimitExceededError` を投げる(RAW 子行が存在するのは
256
+ この場所だけ・SCP に throw が無いため)。
257
+ - **cross-DB relation**: 文が属する DB 名(`ExecOptions.db`)は relation の **target モデル**から解決され
258
+ (`` `connectionOf` (`src/scp/decorator-adapter.ts`) ``)、接続ルーティング(`src/scp/connection-routing.ts`)
259
+ が named connection の reader/writer 対を選ぶ。未登録名は loud(silent に別 DB へ走らせない)。
260
+ - ホストオブジェクト化は graphddb と同形の `` `hydrate` (`src/scp/typed-object.ts`) `` factory
261
+ (relation 解決の後・`null` には適用しない)。
262
+
263
+ ---
264
+
265
+ ## 6. 書込 — 単文エンドポイント / バッチ / 手続きトランザクション
266
+
267
+ 書込は宣言エンドポイントとして表現する。ライブラリが関連への波及を自動導出することはしない(波及が要るなら
268
+ 利用者が書く)。
269
+
270
+ - **単文書込** — `create` / `update` / `delete`。方言別 SQL は makesql で焼き込まれ、`RETURNING` を宣言
271
+ すれば書込んだ行を返す(MySQL は RETURNING 非対応のため接続アダプタが宣言 PK で再 SELECT する・
272
+ `src/scp/makesql/mysql-returning.ts`)。
273
+ - **バッチ書込** — `createMany` / `updateMany` / `deleteMany`。1論理オペレーションが N 個のグループ文を
274
+ 生む。バッチ SQL は v1 ビルダから byte-copy し、**gate-free** な transaction plan(`` `deriveBatchPlan`
275
+ (`src/scp/makesql/tx.ts`) ``。`entityFrom` null・全文 `role:'body'`)へ落として、全言語 runtime が同一の
276
+ per-statement tx ループで実行する。書込列順は **canonical(アルファベット)**に正規化される(v2 write 経路
277
+ の SSoT・命令 `DBModel._insert` と一致。`src/scp/makesql/compile-crud.ts` / `src/scp/makesql/tx.ts`)。
278
+ - **複数文をまたぐ原子性** — 利用者の `DBModel.transaction(fn)` 手続き境界で束ねる。境界内は
279
+ read-your-writes(未コミット行が同一接続で見える)と rollback を保証するので、「前提の読み → その結果に
280
+ 依存する書込 → 例外での短絡」は手で書く(宣言的な gate-first / tx-DAG 導出は持たない)。実行は
281
+ `` `executeTransactionAsync` (`src/scp/makesql/tx.ts`) `` — ランタイムは per-statement のゲート短絡
282
+ primitive(`GateRule`)を運べるが、宣言エンドポイントはゲートを一切 emit しない(batch plan は gate-free)。
283
+
284
+ ```ts
285
+ await DBModel.transaction(async () => {
286
+ const author = await User.findOne([[User.email, 'a@x.com']]); // 前提の読み
287
+ if (!author) throw new Error('author not found'); // 短絡 = throw → 全体 rollback
288
+ const post = await Post.create({ author_id: author.id, title: 't' }, { returning: true });
289
+ await Comment.create({ post_id: post.id, body: 'c' }); // 直前の書込の id に依存
290
+ });
291
+ ```
292
+
293
+ ---
294
+
295
+ ## 7. 宣言 → SCP 制限 TS → 生成物
296
+
297
+ 宣言エンドポイント(§2.2)は `` `emitBehaviorModule` (`src/scp/emit/emitter.ts`) `` で **SCP 制限 TS** へ
298
+ lowering される。emitter は **自分では何もコンパイルしない**: emit される全成果物は既存の集約点が生み、
299
+ emitter はそれを TypeScript として **RENDER するだけ**である。
300
+
301
+ | emit される物 | 由来 |
302
+ |---|---|
303
+ | read SQL + param 順 | `` `compileSelect` (`src/scp/makesql/compile-select.ts`) `` |
304
+ | WHERE テキスト(全体/断片) | `` `compileWhere` (`src/scp/makesql/compile.ts`) `` |
305
+ | 静的 IN リストの membership | `` `inListPredicate` (`src/scp/makesql/json-array.ts`) ``(PG `= ANY(?)`) |
306
+ | write SQL + param 順 | `` `compileWriteNode` (`src/scp/makesql/tx.ts`) `` |
307
+ | relation batch SQL + キー | `` `compileRelationOp` (`src/scp/relation.ts`) `` |
308
+ | 行型 | `` `deriveReadRow` (`src/scp/makesql/outtype.ts`) `` |
309
+ | 列 SQL 型 | `` `deriveModelColumns` (`src/scp/decorator-adapter.ts`) `` |
310
+ | リーフカタログ | `` `Db` (`src/scp/leaf-transport.ts`) `` |
311
+
312
+ 生成された SCP 制限 TS は `bc generate --from` が読み、go / rust / py / php / ts の native モジュールへ
313
+ 落ちる(リーフ配線は bc が自動生成)。**制御構造は bc 準拠のネイティブ TS**(`?:` / `&&` / `.map`)で、
314
+ 新語彙を足さない。lower 可能サブセット外の自由 SQL は **契約付き Raw SQL**(`QueryView` #98 の派生 CTE
315
+ = `WITH <alias> AS (<query>) SELECT … FROM <alias>` を含む・`src/scp/emit/endpoint.ts` の `QueryView`)。
316
+
317
+ ---
318
+
319
+ ## 8. 生成物と実行時の断片モデル(可搬 IR は emit しない)
320
+
321
+ **litedbmodel は可搬 IR ドキュメントを emit しない**(`src/scp/index.ts`: `no runtime IR, no programmatic
322
+ compile`)。「中間 IR の形」ではなく、次の2つが実体である:
323
+
324
+ 1. **静的宣言エンドポイント**(read + 単文 write)は `emitBehaviorModule` が **方言 SQL を焼き込んだ SCP
325
+ 制限 TS** へ落とし、`bc generate` が native 化する。component-graph IR は bc が所有し、litedbmodel は
326
+ SCP 制限 TS を生むだけ。方言は **TS 側で一度だけ**解決される(§10)。
327
+ 2. **ランタイム write/tx 経路**(`DBModel.transaction` と batch)は bc catalog leaf
328
+ `` `makeSQL` (`src/scp/makesql/makesql.ts`) `` を通る。`makeSQL(sql, params, skip?)` は **1つの** catalog
329
+ component で、`sql` はチューニング済み方言テキスト(`?` プレースホルダ、v1 と byte 一致)、`params` は
330
+ 束ねる値または **ネストした `makeSQL`**(サブクエリ)、`skip?` は presence 条件。`= ANY` / `LATERAL` /
331
+ `UNNEST` / cast / batch は **全て `sql` 内のテキスト**でモデル化しない(`makesql.ts`: `never modeled`)。
332
+
333
+ **動的 WHERE(SKIP)の断片語彙**(CLAUDE.md §2 準拠・`src/scp/leaf-transport.ts`):
334
+
335
+ - 断片は **SQL テキスト + params + SKIP フラグ**だけ(中間 IR 語彙を作らない)。1断片 =
336
+ `` `DynamicWhereFrag` (`src/scp/leaf-transport.ts`) `` = `{ skipped, sql, params }` の均質な struct で、
337
+ `cond`→null のバリアント要素にしない(native-codegen エミッタが拒否する形)。
338
+ - optional 述語を宣言した read だけが `` `DynamicWherePlan` (`src/scp/leaf-transport.ts`) `` を運ぶ。bounded
339
+ 述語は emit 時に静的 WHERE へ lower される(native-clean)。plan は head / lead / tail / tailParams を持ち、
340
+ リーフ(`` `assembleDynamicWhere` (`src/scp/leaves.ts`) ``)が生存断片を連結する(文字列 scan はしない)。
341
+ - **`?`→`$N` はプレースホルダ変換であって最終 SQL 確定後**(PG のみ・左から機械変換1パス)。SKIP で形が
342
+ 変わるため生成段では確定できない。`` `toDollarPlaceholders` (`src/scp/dialect.ts`) `` が最終フラットテキストを
343
+ 一度だけ走査する(番号振り直し問題は設計から消滅)。ランタイムの `` `renderPlaceholders`
344
+ (`src/scp/makesql/handler.ts`) `` は quote-aware(文字列リテラル内の `?` はプレースホルダにしない)。
345
+ - **IN リストの件数展開もランタイム/ドライバ側**: 非空配列は方言のサーバ側展開(PG `= ANY(?)`・MySQL
346
+ `JSON_TABLE`・SQLite `json_each`)で **ONE param・静的テキスト**、**空配列は `1 = 0`**(param なし・v1 と一致。
347
+ `src/scp/makesql/json-array.ts`)。
348
+
349
+ ---
350
+
351
+ ## 9. 実行経路 — コンパイル経路は1本
352
+
353
+ **コンパイル経路は1本**(CLAUDE.md §1): デコレータ付きモデル + 宣言エンドポイント →
354
+ `emitBehaviorModule` → SCP 制限 TS → `bc generate --from` → go / rust / py / php / ts native。リーフ配線は
355
+ bc が自動生成する。実行時のサーフェスは **3本の op 非依存リーフ**(`src/scp/leaf-transport.ts` の
356
+ `Db.executeSQL` / `Db.pluck` / `Db.group`)に閉じ、方言 SQL はこの3本が運ぶテキスト+パラメータで表現する。
357
+
358
+ <!--@embedoc:code_snippet file="src/scp/leaf-transport.ts" grep="^ @leaf static"-->
359
+ ```ts
360
+ @leaf static executeSQL(sql: string, params: WireValue[], opts?: ExecOptions | null): WireValue[] { return declarationStub('executeSQL', [sql, params, opts]); }
361
+ @leaf static pluck(rows: WireValue[], col: string[]): WireValue[] { return declarationStub('pluck', [rows, col]); }
362
+ @leaf static group(parents: WireValue[], children: WireValue[], pk: string[], fk: string[], into: string, single: boolean): WireValue[] { return declarationStub('group', [parents, children, pk, fk, into, single]); }
363
+ ```
364
+ <!--@embedoc:end-->
365
+
366
+ - **`executeSQL`** — 唯一の SQL 転送。`sql` + `params` に加えて任意の1制御 struct `ExecOptions`(どの DB で
367
+ 走るか・read/write モード・SKIP plan・relation ガード)を運ぶ。plain read は `opts` を省略(native-clean)。
368
+ - **`pluck`** — relation キー抽出(dedup・非 null のキー集合)。
369
+ - **`group`** — relation 整形(子を親の `into` にネスト・N+1 なしにする in-memory grouping)。
370
+
371
+ **真のアドホッククエリ**(形が実行時にしか決まらない `find({ where })`)は静的 SQL を持たないので **v1
372
+ 命令パス**で走る(`src/scp/emit/endpoint.ts`)。SKIP は「動的だから v1」ではない — optional 述語は §8 の
373
+ 断片モデルで codegen 経路に載る。
374
+
375
+ 3言語グループの実行モデルは bc の native codegen が決める(go/rust は typed-native の直呼び、py/php は bc
376
+ runtime-core `runBehavior` に生成モジュールの IR リテラルを渡す形)。いずれも **同一コンパイラ・同一 native
377
+ モジュール**を共有するので、経路差による意味論のズレは構造上生じない。
378
+
379
+ ---
380
+
381
+ ## 10. 多言語 Runtime と Conformance
382
+
383
+ - 各言語 runtime = **bc runtime-core(共有)+ litedbmodel SQL runtime**(driver + assembly + dialect)。
384
+ - Conformance(bc の golden 方式): 同一エンドポイント + 同一入力 → **同一 SQL テキスト + 同一 assembly 結果**。
385
+ - **分解**: 方言軸はコンパイル時に **TS 側で1回検証**(`src/scp/dialect.ts` の doc-comment: `方言軸は
386
+ コンパイル時に TS 側で1回検証`。既存 SqlBuilder 資産 + golden SQL)。言語軸は「同一入力 → 同一 SQL +
387
+ 同一結果」の機械検証。→ conformance 行列が **「方言 3 × 言語 5」から「3 + 5」に分解**する。
388
+ - 実 DB conformance は dockerized PostgreSQL + MySQL(SQLite はインプロセス参照)で、5言語 runtime が同一
389
+ ベクタを replay する。
390
+
391
+ ---
392
+
393
+ ## 11. Consumer 実装点(litedbmodel が書くもの / bc から得るもの)
394
+
395
+ **litedbmodel v2 が実装する(`src/scp/index.ts` の4点の具体化):**
396
+
397
+ 1. **Catalog leaf**: ランタイム経路の `` `makeSQL` (`src/scp/makesql/makesql.ts`) ``(1 component)と、codegen
398
+ 経路の3本 op 非依存リーフ `` `Db` (`src/scp/leaf-transport.ts`) ``。
399
+ 2. **Handler**: catalog leaf の唯一の consumer 実装点(`` `renderPlaceholders` を含む
400
+ `src/scp/makesql/handler.ts` ``)— bind params → SQL 実行 → 行→論理モデル assembly。副作用/driver/接続は
401
+ ここに閉じる(IR は実装を運ばない)。
402
+ 3. **Backend Compile**: 宣言エンドポイント → 方言 SQL + param 順(`src/scp/makesql` の `compile-*`)。
403
+ 4. **Error Mapping**: driver エラー → SCP Failure。`` `SqlFailure` (`src/scp/errors.ts`) `` は安定した `kind`
404
+ (`constraint_violation` / `foreign_key_violation` / `retryable` / `driver_error`)と bc の `PolicyKind`
405
+ (fail / retry / continue)を運ぶ。未知コードは loud(silent catch-all にしない)。
406
+ 5. **各言語ランタイム**: 実行コンテキスト・接続ルーティング・relation grouping・列型 de-box・typed-object。
407
+
408
+ **bc から得る(実装しない):** component-graph IR 構造・Expression 評価・Execution Plan 実行・
409
+ `runBehavior` / `validateEnvelope`・codegen 基盤・conformance runner。
410
+
411
+ ---
412
+
413
+ ## 12. TS 公開 API の v1 → v2 移行(破壊的)
414
+
415
+ | 面 | v1 | v2 |
416
+ |---|---|---|
417
+ | CRUD + condition タプル + SKIP | Active Record | ほぼ不変(内部が codegen/命令 経路に分かれる) |
418
+ | `await post.author`(lazy) | prototype getter(Promise) | 残す(getter → 事前コンパイル relation op 起動・§5) |
419
+ | 結果オブジェクト | DBModel インスタンス | **typed-object**(own props はデータのみ・§4) |
420
+ | インスタンスメソッド | クラスメソッド | typed-object には無い → `hydrate: (raw)=>new Domain(raw)` で回復 |
421
+ | 完全動的 Raw SQL | `execute`/`query` | 契約付き Raw SQL(`QueryView` / v1 命令パス) |
422
+ | 書込列順 | 挿入順(v1.x で保全) | **canonical アルファベット順**(v2 write 経路の SSoT・§6) |
423
+
424
+ 破壊の中心は「結果がインスタンス → typed-object」。メソッドは `hydrate` で回復。v1.x はメンテブランチ
425
+ `v1.x` で保全する。
426
+
427
+ ---
428
+
429
+ ## 13. DB 挙動差 — SQL テキストを超える差の closeable-by-convention 線
430
+
431
+ SQL テキストが一致しても DB 挙動が一致するとは限らない(NULL 順序・照合・timezone・浮動小数)。これらは
432
+ **「方言コンパイル時規約」で閉じられる線の内側だけ**を仕様が保証する(`src/scp/dialect.ts` の doc-comment:
433
+ `spec §13 closeable-by-convention line`)。
434
+
435
+ - **機械的に閉じる規約は NULL 順序の決定性1つ**: `` `orderByNulls` (`src/scp/dialect.ts`) `` が
436
+ `ORDER BY … NULLS FIRST/LAST` 要件を方言別にレンダーする。PostgreSQL / SQLite(3.30+) は native
437
+ `NULLS FIRST/LAST`、MySQL は先頭に `<expr> IS NULL` ソートキーを足して等価に emulate する。
438
+ - **照合(collation)・timezone・浮動小数の意味は明示的にスコープ外**(silent に既定へ潰さず、規約の外だと
439
+ ドキュメントする)。
440
+ - プレースホルダ・INSERT conflict 節・guard INSERT など、SQL テキストの方言差はすべて `Dialect` 戦略
441
+ (`src/scp/dialect.ts` の凍結レコード `SQLITE` / `POSTGRES` / `MYSQL`)に閉じ、エンジンコードに散在する
442
+ `?:` を置かない。
443
+
444
+ ---
445
+
446
+ ## 14. バージョニング / エコシステム位置づけ
447
+
448
+ - **v2.0 系(破壊的)**。v1.x はメンテブランチ `v1.x` で保全(別トラック・v1 の SQL は byte 保全)。
449
+ - litedbmodel v2 = behavior-contracts の **SQL バックエンド consumer**(graphddb=DynamoDB と対)。IR /
450
+ Expression / runtime-core / codegen / conformance を共有し、Backend Compile + Handler + Catalog leaf +
451
+ Error Mapping のみを供給する。
452
+ - リリースは **5レジストリ**(npm / crates.io / PyPI / Go-tag / Packagist)。
@@ -2,6 +2,8 @@
2
2
 
3
3
  **対象**: litedbmodel v2 → beta(v1 parity)Epic #74 の基盤フェーズ。tx(#69)・middleware(#70)・reader/writer/複数DB(#72)・config(#72) の全てがこの上に載る。**5 native 言語(TS/rust/go/py/php)共通契約**。設計参照: v1 TS(`AsyncLocalStorage`)・v1 rust(`litedbmodel.rs` `PoolTransaction`)・graphddb。
4
4
 
5
+ > **現況注記(#227 後)**: 本節 §1/§2 が「ctx を通す入口」として挙げる `executeBundle` / `executeTransactionBundle` / `executeReadGraph`(自前の SqlBundle/ReadGraph 実行系)は **#227 で全削除された**。ctx が通る唯一の実行経路は BC 生成 native がバインドする **leaf transport(`executeSQL`/`pluck`/`group`)+中央 execute/run/runGuarded seam** に一本化されている。以下の記述は Phase A 設計当時の出発点(seam 導入前)として読むこと。seam 自体(ExecutionContext / execute / run / withTransaction)は現行のまま。
6
+
5
7
  ---
6
8
 
7
9
  ## 1. 現状(seam の素地と欠落)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litedbmodel",
3
- "version": "2.2.2",
3
+ "version": "2.2.3",
4
4
  "description": "A lightweight TypeScript ORM for PostgreSQL, MySQL, and SQLite with Active Record pattern",
5
5
  "main": "dist/index.cjs",
6
6
  "types": "dist/index.d.ts",
@@ -35,7 +35,7 @@
35
35
  "test": "vitest run",
36
36
  "test:unit": "vitest run test/unit",
37
37
  "test:integration": "TEST_DB_HOST=localhost vitest run test/integration",
38
- "parity:capture": "TEST_DB_HOST=localhost TEST_DB_PORT=5433 vitest run test/parity/v1-sql-golden.test.ts",
38
+ "parity:capture": "PARITY_CAPTURE_WRITE=1 TEST_DB_HOST=localhost TEST_DB_PORT=5433 vitest run test/parity/v1-sql-golden.test.ts",
39
39
  "parity:scp": "vitest run test/parity/v2-scp-parity.test.ts",
40
40
  "test:watch": "vitest",
41
41
  "test:coverage": "vitest run --coverage",
@@ -47,14 +47,25 @@
47
47
  "docker:down": "docker compose -f docker-compose.test.yml down",
48
48
  "docker:reset": "docker compose -f docker-compose.test.yml down -v && docker compose -f docker-compose.test.yml up -d",
49
49
  "test:integration:docker": "npm run docker:up && sleep 3 && npm run test:integration; npm run docker:down",
50
- "test:ci": "vitest run test/unit",
51
50
  "sync:versions": "node scripts/sync-versions.mjs",
52
51
  "sync:versions:check": "node scripts/sync-versions.mjs --check",
53
52
  "deps:check": "node scripts/check-no-local-deps.mjs",
54
53
  "gates:check": "node scripts/check-reachable-test-gates.mjs",
54
+ "go:fmt:check": "node scripts/check-go-fmt.mjs",
55
+ "ts:test": "node scripts/check-ts-test-skips.mjs",
56
+ "py:test": "node scripts/check-python-test-skips.mjs",
57
+ "php:test": "node scripts/check-php-test-skips.mjs",
58
+ "go:test": "node scripts/check-go-test-skips.mjs",
59
+ "rust:test": "node scripts/check-rust-test-skips.mjs",
55
60
  "pkg:check": "node scripts/check-package-entrypoints.mjs",
61
+ "types:check": "node scripts/check-test-types.mjs",
62
+ "tracked:check": "node scripts/check-tracked-files.mjs",
63
+ "spec:check": "node scripts/check-spec-refs.mjs",
56
64
  "vendor:bc-php": "node scripts/vendor-behavior-contracts-php.mjs",
57
65
  "vendor:bc-php:check": "node scripts/vendor-behavior-contracts-php.mjs --check",
66
+ "go:dispatch:check": "LITEDBMODEL_LIVEDB_VECTORS=\"$PWD/conformance/vectors-livedb/livedb.json\" sh -c 'cd go && exec go run ./conformance/livedb --check-coverage'",
67
+ "rust:dispatch:check": "LITEDBMODEL_LIVEDB_VECTORS=\"$PWD/conformance/vectors-livedb/livedb.json\" sh -c 'cd rust && exec cargo run --quiet -p livedb_runner --features livedb -- --check-coverage'",
68
+ "conformance:dispatch:check": "npm run go:dispatch:check && npm run rust:dispatch:check",
58
69
  "conformance:gen": "vitest run --config conformance/vitest.config.ts",
59
70
  "conformance:gen:livedb": "vitest run --config conformance/vitest.livedb.config.ts",
60
71
  "conformance:check:livedb": "LIVEDB_GEN_MODE=check vitest run --config conformance/vitest.livedb.config.ts",