graphddb 0.2.4 → 0.2.5
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/README.md +314 -305
- package/dist/{chunk-GCPEUPAA.js → chunk-4TYK3AV6.js} +671 -155
- package/dist/{chunk-LNESVUTO.js → chunk-ITHQ2EDH.js} +1 -1
- package/dist/{chunk-IQEOJVHI.js → chunk-SGYBE2OV.js} +378 -49
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +167 -3
- package/dist/index.js +3 -3
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +2 -2
- package/dist/{types-BEeQ_nSd.d.ts → types--tdHK3vi.d.ts} +674 -212
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
# GraphDDB
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**DynamoDB のための型安全な Graph Query Runtime**
|
|
4
4
|
|
|
5
|
-
GraphDDB
|
|
6
|
-
PK
|
|
5
|
+
GraphDDB は DynamoDB のネイティブなアクセスパターンを GraphQL ライクなクエリモデルにマッピングする。
|
|
6
|
+
PK、GSI、Projection、Relation、Cursor、そして操作の上限(limit)はすべて隠さず可視に保たれる。
|
|
7
7
|
|
|
8
|
-
GraphDDB
|
|
9
|
-
|
|
8
|
+
GraphDDB は ORM ではない。
|
|
9
|
+
TypeScript の型を使って DynamoDB のアクセスパターンを定義・検証・実行するための Runtime である。
|
|
10
10
|
|
|
11
|
-
## Why GraphDDB?
|
|
11
|
+
## 🤔 Why GraphDDB?
|
|
12
12
|
|
|
13
|
-
DynamoDB
|
|
14
|
-
RDBMS
|
|
15
|
-
ORM
|
|
16
|
-
|
|
13
|
+
DynamoDB は高速でスケーラブルだが、誤った設計をしやすい。チームはしばしば DynamoDB を
|
|
14
|
+
RDBMS のように扱い —— エンティティごとに1テーブル、アプリケーションコード内での join、
|
|
15
|
+
ORM 抽象の裏に隠れたその場しのぎのアクセスパターン —— その結果、不安定なパフォーマンス、
|
|
16
|
+
不要な GSI、不明瞭なサービス境界に陥る。
|
|
17
17
|
|
|
18
|
-
GraphDDB
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
GraphDDB は逆のアプローチを取る。DynamoDB のアクセスパターンを **コードに直接** 表現し、
|
|
19
|
+
PK/GSI、projection、relation、cursor、操作の上限を可視に保つ。アクセスパターンと relation が
|
|
20
|
+
明示的になり、クエリプランが検査可能になり、よく設計されたアクセスパターンが自然と
|
|
21
|
+
サービス境界に整合する。GraphDDB は DynamoDB の設計を教えるのではない —— 正しい設計を、
|
|
22
|
+
コードを書く上で最も簡単な道にする。
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
access patterns:
|
|
24
|
+
これは GraphQL のクエリモデルと DynamoDB のアクセスパターンの自然な対応関係を通じて実現される:
|
|
26
25
|
|
|
27
26
|
```text
|
|
28
27
|
GraphQL Query Model DynamoDB
|
|
@@ -33,33 +32,33 @@ connection ----> Cursor pagination
|
|
|
33
32
|
complexity ----> Operation limits
|
|
34
33
|
```
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
selection set
|
|
38
|
-
|
|
35
|
+
Partition Key は Graph のエントリポイントであり、relation は明示的な traversal パスであり、
|
|
36
|
+
projection は selection set である。GraphDDB はクエリ言語を発明しない —— よく設計された
|
|
37
|
+
アクセスパターンを直接実行し、plan・limit・relation 解決を通じて曖昧さを可視化する。
|
|
39
38
|
|
|
40
|
-
## Install
|
|
39
|
+
## 📦 Install
|
|
41
40
|
|
|
42
41
|
```bash
|
|
43
42
|
npm install graphddb
|
|
44
43
|
```
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
AWS SDK v3 は peer dependency である —— 使用するクライアントをインストールすること:
|
|
47
46
|
|
|
48
47
|
```bash
|
|
49
48
|
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
|
|
50
49
|
```
|
|
51
50
|
|
|
52
|
-
`tsx`
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
`tsx` は optional な peer dependency で、コード生成 CLI(`graphddb generate …`)を
|
|
52
|
+
TypeScript の定義ファイルに対して直接実行する場合にのみ必要となる。ライブラリの利用者には
|
|
53
|
+
不要である(そのため esbuild がランタイムバンドルに混入することはない):
|
|
55
54
|
|
|
56
55
|
```bash
|
|
57
56
|
npm install -D tsx
|
|
58
57
|
```
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
Node.js ≥ 22 が必要。
|
|
61
60
|
|
|
62
|
-
## Quick Start
|
|
61
|
+
## 🚀 Quick Start
|
|
63
62
|
|
|
64
63
|
```ts
|
|
65
64
|
import { DDBModel, model, string, key, k } from 'graphddb';
|
|
@@ -77,74 +76,76 @@ class UserModel extends DDBModel {
|
|
|
77
76
|
}
|
|
78
77
|
const User = UserModel.asModel();
|
|
79
78
|
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
79
|
+
// DynamoDB クライアントは一度だけ接続する。GraphDDB が throttle / transient の retry を
|
|
80
|
+
// 所有するため(後述の "Retry & throttling" を参照)、クライアントは `maxAttempts: 1` に設定し、
|
|
81
|
+
// 二重 retry(SDK × ライブラリ)を避けること。
|
|
83
82
|
DDBModel.setClient(new DynamoDBClient({ maxAttempts: 1 }));
|
|
84
83
|
|
|
85
|
-
// Read
|
|
84
|
+
// Read —— 結果の型に現れるのは select したフィールドのみ。
|
|
86
85
|
const user = await User.query({ userId: 'alice' }, { name: true });
|
|
87
86
|
|
|
88
|
-
//
|
|
87
|
+
// 単一アイテムの書き込み —— DynamoDB API にちなんで名付けられた raw な base 操作。
|
|
89
88
|
await User.putItem({ userId: 'alice', name: 'Alice' });
|
|
90
89
|
|
|
91
|
-
//
|
|
90
|
+
// 複数アイテムの atomic な書き込み —— 統一 envelope(デフォルト `mode: 'transaction'`)。
|
|
92
91
|
await DDBModel.mutate({
|
|
93
92
|
user: { update: User, key: { userId: 'alice' }, input: { name: 'Alice B.' } },
|
|
94
93
|
});
|
|
95
94
|
```
|
|
96
95
|
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
クロスサービス contract、Python bridge、複数 fragment の atomic mutation などについては、
|
|
97
|
+
以下の各セクションおよび [`docs/spec.md`](./docs/spec.md) を参照。
|
|
99
98
|
|
|
100
|
-
## Retry & throttling
|
|
99
|
+
## 🔁 Retry & throttling
|
|
101
100
|
|
|
102
|
-
GraphDDB
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
GraphDDB は **すべての** 単一アイテム操作(`GetItem` / `Query` / `PutItem` / `UpdateItem` /
|
|
102
|
+
`DeleteItem`)、relation read の fan-out、そして `TransactWriteItems` に対して、throttle /
|
|
103
|
+
transient エラーの retry を行う —— 設定不要で、すぐに使える。デフォルトのポリシーは
|
|
104
|
+
full jitter 付きの指数バックオフ(`min(1000, 50·2^(n-1))` ms)で、最大 10 回まで、
|
|
105
|
+
throttle / 5xx / transient エラーのみを retry する(`ValidationException` や
|
|
106
|
+
条件チェック失敗は決して retry しない)。キャンセルされた transaction は理由ごとに
|
|
107
|
+
検査される: `ConditionalCheckFailed` の理由は即座に throw し、純粋に throttle による
|
|
108
|
+
キャンセルは retry される。
|
|
110
109
|
|
|
111
|
-
>
|
|
112
|
-
> `UnprocessedKeys` / `UnprocessedItems` retry
|
|
113
|
-
>
|
|
110
|
+
> これはライブラリ自身の **error retry** であり、`batchGet` / `batchWrite` が既に行う
|
|
111
|
+
> 部分バッチの `UnprocessedKeys` / `UnprocessedItems` retry とは別物である。両者は同じ
|
|
112
|
+
> バックオフのランプを使うが、独立したメカニズムである。
|
|
114
113
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
**クライアントは `maxAttempts: 1` に設定すること。** ライブラリが retry を所有する以上、
|
|
115
|
+
AWS SDK をデフォルト(`maxAttempts: 3`)のままにすると、GraphDDB の下層で
|
|
116
|
+
*乗算的に* retry してしまう。ライブラリを単一の真実の源とすること:
|
|
118
117
|
|
|
119
118
|
```ts
|
|
120
119
|
DDBModel.setClient(new DynamoDBClient({ maxAttempts: 1 }));
|
|
121
120
|
```
|
|
122
121
|
|
|
123
|
-
GraphDDB
|
|
122
|
+
GraphDDB は渡されたクライアントを決して変更しない —— この設定はあなたの責任である。
|
|
124
123
|
|
|
125
|
-
|
|
124
|
+
ポリシーはグローバルに、あるいは呼び出しごとに調整できる:
|
|
126
125
|
|
|
127
126
|
```ts
|
|
128
127
|
import { DDBModel } from 'graphddb';
|
|
129
128
|
|
|
130
|
-
//
|
|
129
|
+
// グローバルな上書き(setClient / setTableMapping と同様)。`null` でデフォルトに戻る。
|
|
131
130
|
DDBModel.setRetryPolicy({
|
|
132
131
|
maxAttempts: 5,
|
|
133
|
-
jitter: 'full', //
|
|
132
|
+
jitter: 'full', // または 'none'
|
|
134
133
|
onRetry: ({ attempt, error, delayMs, operation }) => {
|
|
135
134
|
console.warn(`retry #${attempt} of ${operation} after ${delayMs}ms`, error);
|
|
136
135
|
},
|
|
137
136
|
});
|
|
138
137
|
|
|
139
|
-
//
|
|
138
|
+
// 任意の操作の options での呼び出しごとの上書き(`RetryPolicy | false`)。`false` で retry 無効。
|
|
140
139
|
await User.putItem({ userId: 'alice', name: 'Alice' }, { retry: false });
|
|
141
140
|
await User.query({ userId: 'alice' }, { name: true }, { retry: { maxAttempts: 3 } });
|
|
142
141
|
```
|
|
143
142
|
|
|
144
|
-
## User Permissions Example
|
|
143
|
+
## 👥 User Permissions Example
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
[user-permissions](./examples/user-permissions/) の例は、ユーザー・グループ・メンバーシップ・
|
|
146
|
+
パーミッションを単一の DynamoDB テーブルでモデル化している。
|
|
147
|
+
ここで GraphDDB の設計が最も分かりやすく見えてくる: テーブルは物理的だが、クエリの表面は
|
|
148
|
+
Graph の形をしている。
|
|
148
149
|
|
|
149
150
|
```text
|
|
150
151
|
UserPermissions table
|
|
@@ -156,12 +157,12 @@ GroupMembership GROUP#<groupId> USER#<userId> USER#<userId>
|
|
|
156
157
|
Permission GROUP#<groupId> PERM#<resource>#<action>
|
|
157
158
|
```
|
|
158
159
|
|
|
159
|
-
`GroupMembership`
|
|
160
|
+
`GroupMembership` は隣接リスト(adjacency list)パターンを使う:
|
|
160
161
|
|
|
161
|
-
- `PK/SK`
|
|
162
|
-
- `GSI1PK/GSI1SK`
|
|
162
|
+
- `PK/SK` はグループのメンバーを読む
|
|
163
|
+
- `GSI1PK/GSI1SK` はユーザーが所属するグループを読む
|
|
163
164
|
|
|
164
|
-
|
|
165
|
+
同じ物理テーブルが、キーを隠すことなく両方向をサポートする。
|
|
165
166
|
|
|
166
167
|
```ts
|
|
167
168
|
const engGroup = await Group.query(
|
|
@@ -180,9 +181,9 @@ const engGroup = await Group.query(
|
|
|
180
181
|
},
|
|
181
182
|
);
|
|
182
183
|
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
184
|
+
// email(GSI)でユーザーを探し、所属グループを一覧し、各グループについてグループ本体と
|
|
185
|
+
// そのパーミッションを取得する。N 件の membership -> 親 Group の解決は単一の
|
|
186
|
+
// BatchGetItem に集約される(N+1 にならない)。
|
|
186
187
|
const alice = await User.query(
|
|
187
188
|
{ email: 'alice@example.com' },
|
|
188
189
|
{
|
|
@@ -209,24 +210,29 @@ const alice = await User.query(
|
|
|
209
210
|
);
|
|
210
211
|
```
|
|
211
212
|
|
|
212
|
-
|
|
213
|
+
最初のクエリは、小さく検査可能な DynamoDB plan に展開される:
|
|
213
214
|
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
```mermaid
|
|
216
|
+
flowchart LR
|
|
217
|
+
Q(["Group.query<br/>groupId: eng"]) --> S1["1 Query<br/>PK=GROUP#eng, SK=META<br/>→ Group"]
|
|
218
|
+
Q --> S2["2 Query<br/>PK=GROUP#eng, SK begins_with USER#<br/>→ members"]
|
|
219
|
+
Q --> S3["3 Query<br/>PK=GROUP#eng, SK begins_with PERM#<br/>→ permissions"]
|
|
220
|
+
S2 -.並列ディスパッチ.- S3
|
|
218
221
|
```
|
|
219
222
|
|
|
220
|
-
|
|
223
|
+
独立した relation のサブクエリ(ここでは `members` と `permissions`)は並列にディスパッチされる。
|
|
224
|
+
2番目のクエリの `belongsTo` 解決は N 件の lookup を1回の `BatchGetItem` にバッチ化する。
|
|
225
|
+
並列ディスパッチを示すタイムラインは [example](./examples/user-permissions/) を参照。
|
|
221
226
|
|
|
222
|
-
## Usage
|
|
227
|
+
## 📖 Usage
|
|
223
228
|
|
|
224
|
-
|
|
229
|
+
以下のスニペットは [user-permissions](./examples/user-permissions/) の例を圧縮したものである。
|
|
225
230
|
|
|
226
231
|
### Entity Definition
|
|
227
232
|
|
|
228
|
-
|
|
229
|
-
**
|
|
233
|
+
TypeScript クラスが単一の真実の源(single source of truth)である。Key と GSI は `k`
|
|
234
|
+
タグ付きテンプレートの **segment** から構築され、key builder の入力型がそのまま
|
|
235
|
+
クエリパラメータの型になる。
|
|
230
236
|
|
|
231
237
|
```ts
|
|
232
238
|
const TABLE = 'UserPermissions';
|
|
@@ -276,18 +282,18 @@ export const User = UserModel.asModel();
|
|
|
276
282
|
export const GroupMembership = GroupMembershipModel.asModel();
|
|
277
283
|
```
|
|
278
284
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
[
|
|
285
|
+
複数 segment の sort key は segment の配列である(例: `sk: [k\`PERM#${c.resource}\`, k\`${c.action}\`]`)。
|
|
286
|
+
partial key はマッチする segment 境界で `begins_with` にコンパイルされる。完全なキーモデルは
|
|
287
|
+
[仕様書](./docs/spec.md) を参照。
|
|
282
288
|
|
|
283
289
|
### GraphQL-like Queries
|
|
284
290
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
291
|
+
第1引数は「どうやってデータを見つけるか」を記述する。
|
|
292
|
+
第2引数は「何を返すか」を記述する。
|
|
293
|
+
Runtime は与えられたフィールド名から、どのインデックスを使うかを解決する。
|
|
288
294
|
|
|
289
295
|
```ts
|
|
290
|
-
//
|
|
296
|
+
// email でユーザーを取得し、group membership へ traverse する。
|
|
291
297
|
const alice = await User.query(
|
|
292
298
|
{ email: 'alice@example.com' },
|
|
293
299
|
{
|
|
@@ -300,11 +306,11 @@ const alice = await User.query(
|
|
|
300
306
|
},
|
|
301
307
|
);
|
|
302
308
|
|
|
303
|
-
//
|
|
309
|
+
// 未定義のキーフィールド -> コンパイルエラー
|
|
304
310
|
User.query({ foo: 'bar' }, { name: true });
|
|
305
311
|
```
|
|
306
312
|
|
|
307
|
-
|
|
313
|
+
返り値の型は `select` から推論される:
|
|
308
314
|
|
|
309
315
|
```ts
|
|
310
316
|
// alice: {
|
|
@@ -328,21 +334,21 @@ const result = await GroupMembership.list(
|
|
|
328
334
|
// result.cursor: string | null
|
|
329
335
|
```
|
|
330
336
|
|
|
331
|
-
### Filtering
|
|
337
|
+
### Filtering(サーバーサイド `filter`)
|
|
332
338
|
|
|
333
|
-
`filter`
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
339
|
+
`filter` は宣言的で型安全な条件であり、DynamoDB の **`FilterExpression`** にコンパイルされ、
|
|
340
|
+
**サーバーサイド** で評価される。AWS AppSync の `ModelFilterInput` をモデルにしている:
|
|
341
|
+
裸の値は等価の省略記法、operator オブジェクトは比較を表現し、`and` / `or` / `not` で
|
|
342
|
+
論理グループを構築する。**エンティティ全体** に対して型付けされるため、`select` に **含まれない**
|
|
343
|
+
属性も参照できる。
|
|
338
344
|
|
|
339
345
|
```ts
|
|
340
346
|
const result = await Order.list(
|
|
341
347
|
{ userId: 'u001' },
|
|
342
|
-
{ orderId: true }, // amount / status
|
|
348
|
+
{ orderId: true }, // amount / status は projection 不要
|
|
343
349
|
{
|
|
344
350
|
filter: {
|
|
345
|
-
status: 'confirmed', // #status = :v (
|
|
351
|
+
status: 'confirmed', // #status = :v (等価の省略記法)
|
|
346
352
|
amount: { gt: 100 }, // #amount > :v
|
|
347
353
|
title: { beginsWith: 'A' }, // begins_with(#title, :v)
|
|
348
354
|
shippedAt: { attributeExists: true },
|
|
@@ -352,44 +358,44 @@ const result = await Order.list(
|
|
|
352
358
|
);
|
|
353
359
|
```
|
|
354
360
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
361
|
+
Relation は対象モデルの `Model.relation(...)` を通じて同じ `filter` を取る。サーバーサイドの
|
|
362
|
+
`FilterExpression` で表現できないロジックには、返された projection に対して
|
|
363
|
+
`result.items.filter(...)` を呼ぶこと —— 各 select フィールドの宣言済みスカラー型が保たれるため、
|
|
364
|
+
`o.amount > 100` はキャスト不要である(組み込みの post-load 述語は存在しない)。
|
|
359
365
|
|
|
360
|
-
|
|
361
|
-
`between
|
|
362
|
-
`attributeType
|
|
363
|
-
|
|
364
|
-
|
|
366
|
+
利用可能な operator(フィールド型ごと): `eq`、`ne`、`ge`、`le`、`gt`、`lt`、
|
|
367
|
+
`between`、`in`、`beginsWith`、`contains`、`notContains`、`attributeExists`、
|
|
368
|
+
`attributeType`、`size`。operator は **フィールドの宣言型によって制約される** ——
|
|
369
|
+
例えば数値フィールドへの `beginsWith` / `contains`、あるいは数値への `gt: 'x'` は
|
|
370
|
+
コンパイルエラーになる。
|
|
365
371
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
`Model.col.<field>`
|
|
372
|
+
operator オブジェクトでは表現できない稀な条件のために、`cond` という raw な escape hatch が
|
|
373
|
+
式 fragment を受け取る。カラム名は bare な文字列ではなく、リファクタ安全な
|
|
374
|
+
`Model.col.<field>` 参照を使わなければならない。値はパラメータ化される:
|
|
369
375
|
|
|
370
376
|
```ts
|
|
371
377
|
filter: cond`${Order.col.amount} > ${100} AND attribute_exists(${Order.col.status})`
|
|
372
378
|
```
|
|
373
379
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
380
|
+
すべての値はパラメータ化され(`ExpressionAttributeValues`)、すべてのカラム名は
|
|
381
|
+
エイリアス化される(`ExpressionAttributeNames`)—— リテラルの補間は存在しないため、
|
|
382
|
+
コンパイルされた式はインジェクション安全であり、`KeyConditionExpression` /
|
|
383
|
+
`ProjectionExpression` とは独立して付与される。
|
|
378
384
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
385
|
+
同じ `cond` fragment は read filter としてだけでなく、書き込みの `condition`
|
|
386
|
+
(`putItem` / `updateItem` / `deleteItem`、transaction item、public command に対して)
|
|
387
|
+
としても受け付けられる。
|
|
382
388
|
|
|
383
|
-
> **RCU
|
|
384
|
-
> DynamoDB
|
|
385
|
-
>
|
|
386
|
-
>
|
|
387
|
-
>
|
|
389
|
+
> **RCU に関する注意:** `FilterExpression` は read capacity を **削減しない** ——
|
|
390
|
+
> DynamoDB はまずマッチするキーを読み、その後に filter する。`limit` は filter の
|
|
391
|
+
> **前** に適用されるため、1ページが `limit` **未満** のアイテムを返すことがある
|
|
392
|
+
> (そしてページ全体が filter で除外された場合、null でない cursor を持つ空ページになる)。
|
|
393
|
+
> 効率的な絞り込みのためにキーを設計し、`filter` は正しさのために使うこと。
|
|
388
394
|
|
|
389
395
|
### Writes
|
|
390
396
|
|
|
391
|
-
|
|
392
|
-
|
|
397
|
+
raw な base テーブルの書き込みは `putItem` / `updateItem` / `deleteItem` である ——
|
|
398
|
+
ライフサイクルのセマンティクスを持たないプリミティブな単一アイテム操作:
|
|
393
399
|
|
|
394
400
|
```ts
|
|
395
401
|
await User.putItem({
|
|
@@ -408,19 +414,18 @@ await GroupMembership.putItem({
|
|
|
408
414
|
await User.updateItem({ userId: 'alice' }, { status: 'disabled' });
|
|
409
415
|
```
|
|
410
416
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
[The in-process unified envelope](#the-in-process-unified-envelope)
|
|
417
|
+
ライフサイクルを意識した単一書き込み —— 条件ゲート、read-back、参照整合的な副作用 ——
|
|
418
|
+
は raw なプリミティブではなく `DDBModel.mutate` を経由する(後述の
|
|
419
|
+
[The in-process unified envelope](#the-in-process-unified-envelope) を参照)。
|
|
414
420
|
|
|
415
421
|
### The in-process unified envelope
|
|
416
422
|
|
|
417
|
-
`DDBModel.query`
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
aliases.
|
|
423
|
+
`DDBModel.query` と `DDBModel.mutate` は、1回の呼び出しで複数の route を実行する単一の
|
|
424
|
+
**alias-map envelope** を取る。これは GraphQL operation の in-process 版である:
|
|
425
|
+
各 alias は名前付きの route であり、結果は同じ alias でキー付けされる。
|
|
421
426
|
|
|
422
|
-
`DDBModel.query(map)`
|
|
423
|
-
|
|
427
|
+
`DDBModel.query(map)` は各 read route を独立かつ並列に実行する —— route 間の
|
|
428
|
+
一貫性はなく、GraphQL の並列フィールド解決とまったく同じである:
|
|
424
429
|
|
|
425
430
|
```ts
|
|
426
431
|
const { user, members } = await DDBModel.query({
|
|
@@ -430,12 +435,12 @@ const { user, members } = await DDBModel.query({
|
|
|
430
435
|
// user: Item | null ; members: { items, cursor }
|
|
431
436
|
```
|
|
432
437
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
`{ consistentRead?, maxDepth? }
|
|
438
|
+
read route の記述子は `{ query | list: Model, key, select, options? }` である
|
|
439
|
+
(`query` / `list` のいずれか一方)。`query` の `options` は
|
|
440
|
+
`{ consistentRead?, maxDepth? }`、`list` の場合は `{ limit?, after?, order?, filter? }` である。
|
|
436
441
|
|
|
437
|
-
`DDBModel.mutate(map, { mode })`
|
|
438
|
-
`{ create | update | remove: Model, key, input?, condition?, result? }
|
|
442
|
+
`DDBModel.mutate(map, { mode })` は write route を実行する。write route の記述子は
|
|
443
|
+
`{ create | update | remove: Model, key, input?, condition?, result? }` である:
|
|
439
444
|
|
|
440
445
|
```ts
|
|
441
446
|
const res = await DDBModel.mutate({
|
|
@@ -444,32 +449,30 @@ const res = await DDBModel.mutate({
|
|
|
444
449
|
}, { mode: 'transaction' });
|
|
445
450
|
```
|
|
446
451
|
|
|
447
|
-
- **`key`**
|
|
448
|
-
transaction
|
|
449
|
-
`BatchWriteItem
|
|
450
|
-
- **`condition`**
|
|
451
|
-
operator
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
-
|
|
458
|
-
all-or-nothing
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
per-field partial success; and `query` routes are parallel independent reads with
|
|
467
|
-
no cross-route consistency.
|
|
452
|
+
- **`key`** は単一のオブジェクト、または **オブジェクトの配列**(key-array bulk)である。
|
|
453
|
+
transaction は `TransactWriteItems` にコンパイルされ、parallel モードは
|
|
454
|
+
`BatchWriteItem`(チャンク分割され、`UnprocessedItems` retry 付き)にコンパイルされる。
|
|
455
|
+
- **`condition`** は `WriteCondition` の書き込みゲートである —— read filter と同じ宣言的
|
|
456
|
+
operator のサブセット(`eq`/`ne`/比較/`between`/`in`/文字列操作/`size`/`attributeType` +
|
|
457
|
+
`and`/`or`/`not`)、レガシーな存在プリミティブ(`notExists` / `attributeExists` /
|
|
458
|
+
`attributeNotExists`)、または raw な `cond` fragment。
|
|
459
|
+
- **`result: { select, options? }`** は書き込んだアイテムを read-back する。省略すると
|
|
460
|
+
その route は void を返す。
|
|
461
|
+
- **`mode: 'transaction'`**(デフォルト)は1回の atomic な `TransactWriteItems` である:
|
|
462
|
+
all-or-nothing のロールバック、失敗時に throw、25 件を超える map はエラーになる
|
|
463
|
+
(決して分割されない)。これは GraphQL の all-or-nothing な atomic 契約である。
|
|
464
|
+
- **`mode: 'parallel'`** は非 atomic である: 各 alias は結果オブジェクト内で
|
|
465
|
+
`{ ok }` | `{ error }` として部分的な成否を報告し、GraphQL のフィールドごとの
|
|
466
|
+
部分成功を反映する。独立した操作は並列に実行され、依存関係は `$.alias.field`
|
|
467
|
+
参照で順序付けられる。
|
|
468
|
+
|
|
469
|
+
GraphQL の用語で言えば: transaction は atomic な all-or-nothing、parallel は非 atomic な
|
|
470
|
+
フィールドごとの部分成功、そして `query` route は route 間の一貫性を持たない並列独立 read である。
|
|
468
471
|
|
|
469
472
|
### Inspect Execution Plans
|
|
470
473
|
|
|
471
|
-
`explain()`
|
|
472
|
-
`select`
|
|
474
|
+
`explain()` は、実行される前の DynamoDB 操作を表示する。
|
|
475
|
+
`select` は `ProjectionExpression` に変換されるため、要求した属性のみが読まれる。
|
|
473
476
|
|
|
474
477
|
```ts
|
|
475
478
|
const plan = GroupMembership.explain(
|
|
@@ -491,7 +494,7 @@ const plan = GroupMembership.explain(
|
|
|
491
494
|
// }
|
|
492
495
|
```
|
|
493
496
|
|
|
494
|
-
|
|
497
|
+
partial な GSI キーマッチの場合、SK 条件は `rangeCondition` として分離される:
|
|
495
498
|
|
|
496
499
|
```ts
|
|
497
500
|
const gsiPlan = GroupMembership.explain(
|
|
@@ -513,33 +516,33 @@ const gsiPlan = GroupMembership.explain(
|
|
|
513
516
|
// }
|
|
514
517
|
```
|
|
515
518
|
|
|
516
|
-
## Core Concepts
|
|
519
|
+
## 🧠 Core Concepts
|
|
517
520
|
|
|
518
521
|
### Entity = Access Pattern
|
|
519
522
|
|
|
520
|
-
|
|
521
|
-
|
|
523
|
+
Entity は ORM モデルではない。
|
|
524
|
+
DynamoDB に対する、境界付けられたアクセス表面(bounded access surface)である。
|
|
522
525
|
|
|
523
|
-
|
|
526
|
+
Entity は、サービス境界内でデータにアクセスする、サポートされたすべての方法を定義する:
|
|
524
527
|
|
|
525
|
-
-
|
|
526
|
-
-
|
|
527
|
-
-
|
|
528
|
-
-
|
|
529
|
-
- traversal
|
|
528
|
+
- 物理キー
|
|
529
|
+
- GSI
|
|
530
|
+
- relation
|
|
531
|
+
- projection
|
|
532
|
+
- traversal ルール
|
|
530
533
|
|
|
531
|
-
|
|
532
|
-
|
|
534
|
+
物理的には、Entity は DynamoDB の item にマップされる。
|
|
535
|
+
アーキテクチャ的には、サービスのその部分に許可されたアクセス表面を定義する。
|
|
533
536
|
|
|
534
537
|
### Key / GSI = Structured Segments
|
|
535
538
|
|
|
536
|
-
`key()`
|
|
537
|
-
|
|
539
|
+
`key()` と `gsi()` は `k` タグ付きテンプレートの **segment** からキーを構築する。builder の
|
|
540
|
+
入力型がキー解決に必要なフィールドを定義し、同時にクエリパラメータの型にもなる。
|
|
538
541
|
|
|
539
542
|
```ts
|
|
540
|
-
//
|
|
541
|
-
// 1.
|
|
542
|
-
// 2.
|
|
543
|
+
// 1つの定義が2つの役割を果たす:
|
|
544
|
+
// 1. 書き込みパス: Entity 保存時に PK/SK を生成
|
|
545
|
+
// 2. クエリパス: User.query({ email: ... }) のパラメータ型を定義
|
|
543
546
|
static readonly emailIndex = gsi<{ email: string }>(
|
|
544
547
|
'GSI1',
|
|
545
548
|
(c) => ({ pk: k`EMAIL#${c.email}`, sk: k`PROFILE` }),
|
|
@@ -547,14 +550,14 @@ static readonly emailIndex = gsi<{ email: string }>(
|
|
|
547
550
|
);
|
|
548
551
|
```
|
|
549
552
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
+
キーは不透明な文字列ではなく構造化された segment であるため、partial key —— sort-key
|
|
554
|
+
segment の先頭プレフィックスのみを与えたもの —— はその segment 境界で `begins_with` に
|
|
555
|
+
コンパイルされる。これが relation と prefix クエリの解決方法である。
|
|
553
556
|
|
|
554
557
|
### Relation = Bound Query
|
|
555
558
|
|
|
556
|
-
|
|
557
|
-
|
|
559
|
+
Relation は、親 Entity の値にバインドされたクエリである。
|
|
560
|
+
同じ解決ロジックが適用される: runtime はフィールド名を Key / GSI 定義に照合する。
|
|
558
561
|
|
|
559
562
|
```ts
|
|
560
563
|
@hasMany(() => GroupMembershipModel, { userId: 'userId' }, {
|
|
@@ -565,154 +568,160 @@ groups!: GroupMembershipModel[];
|
|
|
565
568
|
|
|
566
569
|
### Field Decorators
|
|
567
570
|
|
|
568
|
-
|
|
569
|
-
|
|
571
|
+
型安全なセマンティック decorator が DynamoDB の属性型を定義する。
|
|
572
|
+
`ClassFieldDecoratorContext` に基づくコンパイル時チェックが、宣言された TypeScript の型と
|
|
573
|
+
DynamoDB の型の不一致を検出する。
|
|
570
574
|
|
|
571
575
|
```ts
|
|
572
576
|
@string userId!: string; // DynamoDB S
|
|
573
577
|
@number amount!: number; // DynamoDB N
|
|
574
|
-
@datetime createdAt!: Date; //
|
|
578
|
+
@datetime createdAt!: Date; // ISO 8601 文字列として保存される
|
|
575
579
|
@boolean isActive!: boolean; // DynamoDB BOOL
|
|
576
580
|
```
|
|
577
581
|
|
|
578
582
|
### Select = Projection + Type Inference
|
|
579
583
|
|
|
580
|
-
|
|
581
|
-
|
|
584
|
+
`select` オブジェクトは DynamoDB の `ProjectionExpression` 生成と TypeScript の返り値型推論の
|
|
585
|
+
両方を制御する。
|
|
586
|
+
要求したフィールドだけが返り、結果の型にもそのフィールドだけが現れる。
|
|
582
587
|
|
|
583
588
|
### Planner = Inspectable Execution Plan
|
|
584
589
|
|
|
585
|
-
|
|
586
|
-
|
|
590
|
+
DynamoDB にアクセスする前に、runtime が実行計画(execution plan)を生成する。
|
|
591
|
+
plan はテスト・デバッグ・RCU 見積もりに利用できる。
|
|
587
592
|
|
|
588
|
-
## Architecture
|
|
593
|
+
## 🏗️ Architecture
|
|
589
594
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
Layer 2: Query Builder
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
DynamoDB DocumentClient wrapper
|
|
606
|
-
-> raw Items
|
|
607
|
-
|
|
608
|
-
Layer 5: Hydrator
|
|
609
|
-
raw Items -> Partial Entity instances
|
|
610
|
-
type conversion, Embedded reconstruction, metadata attachment
|
|
595
|
+
GraphDDB のクエリは 5 つのレイヤーを通って実行される:
|
|
596
|
+
|
|
597
|
+
```mermaid
|
|
598
|
+
flowchart TD
|
|
599
|
+
L1["🧩 Layer 1: Schema Definition<br/>Entity / Field / Key / GSI / Relation / Embedded"]
|
|
600
|
+
L2["🔧 Layer 2: Query Builder<br/>型安全な DSL: key 解決 / select バリデーション"]
|
|
601
|
+
L3["🗺️ Layer 3: Query Planner<br/>Relation traversal / Get vs Query vs BatchGet 判定<br/>Projection 生成 / GSI 選択"]
|
|
602
|
+
L4["⚡ Layer 4: Executor<br/>DynamoDB DocumentClient ラッパー"]
|
|
603
|
+
L5["💧 Layer 5: Hydrator<br/>型変換 / Embedded 再構築 / メタデータ付与"]
|
|
604
|
+
|
|
605
|
+
L1 -->|EntityMetadata Registry| L2
|
|
606
|
+
L2 -->|ExecutionPlan| L3
|
|
607
|
+
L3 -->|"DynamoDBOperation[]"| L4
|
|
608
|
+
L4 -->|raw Items| L5
|
|
609
|
+
L5 -->|Partial Entity インスタンス| OUT(["✅ 型付き結果"])
|
|
611
610
|
```
|
|
612
611
|
|
|
613
|
-
## Design Rules
|
|
612
|
+
## 📐 Design Rules
|
|
614
613
|
|
|
615
|
-
|
|
|
614
|
+
| ルール | 内容 |
|
|
616
615
|
|------|-------------|
|
|
617
|
-
| Service Boundary First |
|
|
618
|
-
| Access Pattern First |
|
|
619
|
-
| Natural Mapping |
|
|
620
|
-
| Inspectable Plans | DynamoDB
|
|
621
|
-
| Single-item query | `query`
|
|
622
|
-
| Explicit limits | `hasMany`
|
|
623
|
-
| Bounded depth | Relation traversal
|
|
624
|
-
| Parallel relations |
|
|
625
|
-
| No Scan | Full table
|
|
626
|
-
| Partial select |
|
|
627
|
-
| Structured keys |
|
|
628
|
-
| Typed consistentRead | `consistentRead`
|
|
629
|
-
| Server-side filter | `filter`
|
|
630
|
-
|
|
631
|
-
## Architectural Boundaries
|
|
632
|
-
|
|
633
|
-
Relation traversal
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
-
|
|
646
|
-
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
- **[
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
`
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
- **[
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
616
|
+
| Service Boundary First | 1テーブルは1エンティティではなく、1つのサービス境界を表す。 |
|
|
617
|
+
| Access Pattern First | テーブルや ORM 風のエンティティをモデル化する前に、DynamoDB のアクセスパターンをモデル化する。 |
|
|
618
|
+
| Natural Mapping | Query / Get / BatchGet / Projection / cursor を GraphQL ライクなクエリの形にマップする。 |
|
|
619
|
+
| Inspectable Plans | DynamoDB の操作と実行計画は、実行前に検査可能である。 |
|
|
620
|
+
| Single-item query | `query` は PK または unique GSI の lookup のみ。複数件返りうるキーには `list` を使う。 |
|
|
621
|
+
| Explicit limits | `hasMany` と `list` は default/max limit の指定が必須。 |
|
|
622
|
+
| Bounded depth | Relation traversal はデフォルトで depth=1。より深い traversal は `maxDepth` で明示的に許可する必要がある。 |
|
|
623
|
+
| Parallel relations | 独立した relation のサブクエリ、BatchGet のチャンク、ネストした解決は並列にディスパッチされる(上限あり)。DynamoDB は HTTP ベースのため、直列化すべきコネクションが存在しない。 |
|
|
624
|
+
| No Scan | Full table scan は禁止。linter が検出・ブロックする。 |
|
|
625
|
+
| Partial select | クエリ結果は partial 型。select したフィールドのみが結果の型に現れる。 |
|
|
626
|
+
| Structured keys | キーは `k` segment テンプレートから構築され、partial key は segment 境界で `begins_with` にコンパイルされる。 |
|
|
627
|
+
| Typed consistentRead | `consistentRead` は PK クエリでのみ利用可能。GSI クエリはコンパイル時に拒否される。 |
|
|
628
|
+
| Server-side filter | `filter` は宣言的な DynamoDB `FilterExpression`(AppSync `ModelFilterInput` 互換)で、エンティティ全体に対して型付けされ、projection されていない属性も参照できる。RCU は削減せず、`limit` はその前に適用される。 |
|
|
629
|
+
|
|
630
|
+
## 🚧 Architectural Boundaries
|
|
631
|
+
|
|
632
|
+
Relation traversal は、Entity 上に定義されたアクセスパスのみをたどる。
|
|
633
|
+
未定義の relation、欠けている GSI、limit のない list、許可された depth を超える traversal は、
|
|
634
|
+
実行前にチェックされる。
|
|
635
|
+
|
|
636
|
+
これにより、境界をまたぐクエリや DynamoDB に不向きなアクセスパターンが、コード上で見えやすくなる。
|
|
637
|
+
コードが人間によって書かれたものであれ、AI によって生成されたものであれ、同じアーキテクチャ上の
|
|
638
|
+
制約が適用される。
|
|
639
|
+
|
|
640
|
+
## 🔍 Linter
|
|
641
|
+
|
|
642
|
+
静的解析が、runtime の前に Entity 定義の問題を検出する:
|
|
643
|
+
|
|
644
|
+
- Scan の使用
|
|
645
|
+
- `hasMany` / `list` での limit 未指定
|
|
646
|
+
- 曖昧な GSI 解決
|
|
647
|
+
- unique GSI に対する query/list の境界違反
|
|
648
|
+
|
|
649
|
+
## 🧱 Beyond the Core
|
|
650
|
+
|
|
651
|
+
上記の query / relation / write API がコアレイヤーである。GraphDDB はさらに以下も提供する:
|
|
652
|
+
|
|
653
|
+
- **[CQRS contracts](./docs/cqrs-contract.md)** —— N+1 安全な cardinality matrix、
|
|
654
|
+
contract をまたぐ合成、context 境界の強制を備えた、public な Query/Command contract モデル。
|
|
655
|
+
- **[Mutation → command derivation](./docs/mutation-command-derivation.md)** —— public な
|
|
656
|
+
Command IF の背後にある、内部的な write-plan 合成 DSL。モデルの書き込みセマンティクス
|
|
657
|
+
(`entityWrites`: 参照整合性、一意性、edge effect、派生カウンタ、outbox event、冪等性)を
|
|
658
|
+
一度宣言すると、`mutation` が1つ以上の write fragment を合成し、コンパイラがそれらを単一の
|
|
659
|
+
atomic な `TransactWriteItems` にマージする。すべての書き込みパスは1つの共有された
|
|
660
|
+
`commitTransaction` オーケストレーションを経由する。
|
|
661
|
+
- **[Opt-in class hydration](./docs/class-hydration.md)** —— `options.hydrate` ファクトリを
|
|
662
|
+
渡すと、read 結果をデフォルトの Typed Plain Object ではなく、ホスト言語のドメインオブジェクトに
|
|
663
|
+
ロードできる。ホスト専用で、bridge SSoT にシリアライズされることはない。Phase 1
|
|
664
|
+
(`query` のトップレベル)は実装済み。`list` / relation ごとの hydration は今後の予定。
|
|
665
|
+
- **[Multi-language (Python bridge)](./docs/python-bridge.md)** —— TypeScript が単一の真実の源
|
|
666
|
+
であり、同じ定義から Python クライアント + runtime を生成し、TS↔Python の conformance スイートで
|
|
667
|
+
歩調を合わせる。
|
|
668
|
+
- **[In-memory testing](./docs/testing.md)** —— `graphddb/testing` は model-mapping、query-plan、
|
|
669
|
+
relation-traversal、CDC のテストを **Docker なしの in-process** で実行する。
|
|
670
|
+
- **[CDC emulator](./docs/cdc-emulator.md)** —— ローカルでの差分集計を駆動・テストするための、
|
|
671
|
+
DynamoDB Streams 相当の変更イベント。
|
|
672
|
+
|
|
673
|
+
## 📚 Documentation
|
|
674
|
+
|
|
675
|
+
| ドキュメント | 内容 |
|
|
675
676
|
|----------|-------------|
|
|
676
|
-
| [Specification](./docs/spec.md) |
|
|
677
|
-
| [CQRS contract layer](./docs/cqrs-contract.md) |
|
|
678
|
-
| [Mutation → command derivation](./docs/mutation-command-derivation.md) |
|
|
679
|
-
| [Class hydration](./docs/class-hydration.md) |
|
|
680
|
-
| [Multi-language / Python bridge](./docs/python-bridge.md) | TS
|
|
681
|
-
| [In-memory test adapter](./docs/testing.md) |
|
|
682
|
-
| [CDC emulator](./docs/cdc-emulator.md) |
|
|
683
|
-
| [Benchmark](./benchmark/RESULTS.md) |
|
|
684
|
-
|
|
685
|
-
## Examples
|
|
686
|
-
|
|
687
|
-
| Example |
|
|
677
|
+
| [Specification](./docs/spec.md) | コア API: entity、structured keys/GSIs、query/filter、relation、batch/transaction、design rule、runtime の挙動。 |
|
|
678
|
+
| [CQRS contract layer](./docs/cqrs-contract.md) | public な Query/Command contract、cardinality matrix、N+1 安全性、contract をまたぐ合成、context 境界。 |
|
|
679
|
+
| [Mutation → command derivation](./docs/mutation-command-derivation.md) | Command IF の背後にある内部的な write-plan 合成 DSL: モデルの write セマンティクス(`entityWrites`)、fragment 合成、atomic な `TransactWriteItems` の導出。 |
|
|
680
|
+
| [Class hydration](./docs/class-hydration.md) | read 結果をホスト言語のオブジェクトにロードする opt-in な `options.hydrate` ファクトリ(Phase 1: `query` トップレベル; Phase 2–3 は今後)。 |
|
|
681
|
+
| [Multi-language / Python bridge](./docs/python-bridge.md) | TS を SSoT としたコード生成と Python runtime; TS↔Python の conformance。 |
|
|
682
|
+
| [In-memory test adapter](./docs/testing.md) | `graphddb/testing` と `MemoryInspector` による Docker 不要の in-process テスト。 |
|
|
683
|
+
| [CDC emulator](./docs/cdc-emulator.md) | 差分集計パターン向けの変更イベント emulator(dev/test)。 |
|
|
684
|
+
| [Benchmark](./benchmark/RESULTS.md) | 手書きの AWS SDK、ElectroDB、DynamoDB Toolbox、OneTable とのライブラリ比較。 |
|
|
685
|
+
|
|
686
|
+
## 💡 Examples
|
|
687
|
+
|
|
688
|
+
| Example | 内容 |
|
|
688
689
|
|---------|-------------|
|
|
689
|
-
| [user-permissions](./examples/user-permissions/) |
|
|
690
|
-
| [aggregate-tree-pattern](./examples/aggregate-tree-pattern/) |
|
|
691
|
-
|
|
692
|
-
## Features
|
|
693
|
-
|
|
694
|
-
- Entity
|
|
695
|
-
- DDBModel
|
|
696
|
-
-
|
|
697
|
-
- CRUD
|
|
698
|
-
|
|
699
|
-
-
|
|
700
|
-
-
|
|
701
|
-
|
|
702
|
-
-
|
|
703
|
-
-
|
|
704
|
-
-
|
|
705
|
-
-
|
|
706
|
-
-
|
|
690
|
+
| [user-permissions](./examples/user-permissions/) | Single Table Design でユーザー・グループ・パーミッションを管理する。隣接リスト、転置インデックス、relation traversal、`explain` を含む。 |
|
|
691
|
+
| [aggregate-tree-pattern](./examples/aggregate-tree-pattern/) | CDC emulator で駆動される差分的なツリー集計(`siteScoreAverage`): dirty 伝播、throttle 付き sweep、再計算。 |
|
|
692
|
+
|
|
693
|
+
## ✨ Features
|
|
694
|
+
|
|
695
|
+
- Entity メタデータ / decorator
|
|
696
|
+
- DDBModel 基底クラス / コネクション管理
|
|
697
|
+
- 型システム(SelectableOf, QueryResult, QueryKeyOf)
|
|
698
|
+
- CRUD(put, update, delete, 条件付き書き込み —— `WriteCondition` は宣言的 filter operator の
|
|
699
|
+
フルセットに加え `cond` raw escape hatch を共有する)
|
|
700
|
+
- Query / List(planner, executor, hydrator, cursor pagination)
|
|
701
|
+
- 型安全な filtering: 宣言的なサーバーサイド `filter`(FilterExpression / AppSync
|
|
702
|
+
`ModelFilterInput` 互換)、加えて `Model.col` のカラム参照と `cond` raw escape hatch
|
|
703
|
+
- 構造化 segment キー(`k` タグ): partial key は segment 境界で `begins_with` にコンパイルされる
|
|
704
|
+
- 説明可能な実行計画
|
|
705
|
+
- Relation(hasMany, belongsTo, hasOne, depth 制限, traversal)
|
|
706
|
+
- BatchGet 最適化
|
|
707
|
+
- 並列 relation 実行(独立したサブクエリ、BatchGet チャンク、ネストした解決を上限付きの
|
|
708
|
+
並列度で同時にディスパッチ)
|
|
709
|
+
- Linter(no-scan, require-limit, query-boundary, GSI ambiguity, 拡張された design rule)
|
|
710
|
+
- ConsistentRead の型制約
|
|
707
711
|
- Transaction DSL
|
|
708
|
-
- Batch
|
|
709
|
-
- CQRS Query/Command contract
|
|
710
|
-
- Mutation → command derivation:
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
712
|
+
- Batch 操作
|
|
713
|
+
- CQRS Query/Command contract レイヤー(public IF, N+1 安全な合成, context 境界)—— [docs](./docs/cqrs-contract.md) を参照
|
|
714
|
+
- Mutation → command derivation: Command IF の背後にある内部的な write-plan 合成 DSL
|
|
715
|
+
(`entityWrites` + `mutation`)、複数 fragment の atomic 合成を1つの `TransactWriteItems` に
|
|
716
|
+
—— [docs](./docs/mutation-command-derivation.md) を参照
|
|
717
|
+
- Opt-in class hydration(`options.hydrate`): read 結果をホストオブジェクトにロードする;
|
|
718
|
+
ホスト専用で、シリアライズされない。Phase 1(`query` トップレベル)実装済み
|
|
719
|
+
—— [docs](./docs/class-hydration.md) を参照
|
|
720
|
+
- Multi-language: TS を SSoT とした Python コード生成器 + runtime、TS↔Python の conformance 付き
|
|
721
|
+
—— [docs](./docs/python-bridge.md) を参照
|
|
722
|
+
- In-memory test adapter(`graphddb/testing`): Docker 不要のユニットテスト —— [docs](./docs/testing.md) を参照
|
|
723
|
+
- CDC emulator: dev/test 向けの DynamoDB Streams 相当の変更イベント —— [docs](./docs/cdc-emulator.md) を参照
|
|
724
|
+
|
|
725
|
+
## 📄 License
|
|
717
726
|
|
|
718
727
|
MIT
|