Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ Mesh v2 supports batch event sending to optimize AWS AppSync Subscription costs
Instead of sending each event individually, events are queued and sent in batches every 250ms.

- **Mutation**: `fireEventsByNode(groupId, domain, nodeId, events: [EventInput!]!)`
- **Subscription**: `onBatchEventInGroup(groupId, domain)`
- **Subscription**: `onMessageInGroup(groupId, domain)`

When receiving a `BatchEvent`, clients calculate the relative offset for each event based on its `firedAt` timestamp to reproduce the original firing interval.
When receiving a `BatchEvent` (via the `batchEvent` field in `onMessageInGroup`), clients calculate the relative offset for each event based on its `firedAt` timestamp to reproduce the original firing interval.

### Performance Impact

Expand All @@ -74,7 +74,9 @@ await client.mutate({
// Receiving batch events
subscription.subscribe({
next: (data) => {
const batch = data.onBatchEventInGroup;
const batch = data.onMessageInGroup.batchEvent;
if (!batch) return;

const sorted = batch.events.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
const baseTime = new Date(sorted[0].timestamp).getTime();

Expand Down
164 changes: 85 additions & 79 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ mutation JoinGroup($groupId: ID!, $nodeId: ID!, $domain: String!) {

### reportDataByNode

ノードがセンサーデータを報告します(`onDataUpdateInGroup` subscription をトリガー)。
ノードがセンサーデータを報告します(`onMessageInGroup` subscription をトリガー)。

```graphql
mutation ReportDataByNode(
Expand Down Expand Up @@ -257,7 +257,7 @@ mutation ReportDataByNode(

### fireEventsByNode

ノードが複数のイベントを一度に送信します(`onBatchEventInGroup` subscription をトリガー)。
ノードが複数のイベントを一度に送信します(`onMessageInGroup` subscription をトリガー)。

```graphql
mutation FireEventsByNode(
Expand Down Expand Up @@ -307,7 +307,7 @@ mutation LeaveGroup($groupId: ID!, $domain: String!, $nodeId: ID!) {

### dissolveGroup

グループを解散します(`onGroupDissolve` subscription をトリガー)。
グループを解散します(`onMessageInGroup` subscription をトリガー)。

```graphql
mutation DissolveGroup($groupId: ID!, $domain: String!, $hostId: ID!) {
Expand All @@ -319,7 +319,7 @@ mutation DissolveGroup($groupId: ID!, $domain: String!, $hostId: ID!) {
}
```

**用途**: ホストがグループ全体を解散する際に使用。すべてのメンバーに `onGroupDissolve` subscription が配信されます。
**用途**: ホストがグループ全体を解散する際に使用。すべてのメンバーに `onMessageInGroup` subscription が配信されます。

**注意**: `dissolveGroup` はホスト専用の操作です。メンバーの退出には `leaveGroup` を使用してください。

Expand Down Expand Up @@ -374,109 +374,115 @@ mutation SendMemberHeartbeat($groupId: ID!, $domain: String!, $nodeId: ID!) {

Mesh v2 は AWS AppSync GraphQL Subscriptions over WebSocket を使用したリアルタイム通知をサポートしています。

### onDataUpdateInGroup
### 重要な変更(Issue smalruby/smalruby3-gui#500 関連)

**目的**: リアルタイムでグループ内のノードデータ更新を購読
**統合された Subscription**: 以前は個別の subscription (`onDataUpdateInGroup`, `onBatchEventInGroup`, `onGroupDissolve`) がありましたが、現在は **`onMessageInGroup`** という単一の subscription に統合されています。

**トリガー**: `reportDataByNode` mutation
この変更により:
- WebSocket ストリームが1つになり、送信順序(Mutation実行順序)が受信側でも保証される
- クライアント実装がシンプルになる
- ネットワーク接続数が削減される

**パラメータ**:
- `groupId: ID!` - 購読するグループ ID
- `domain: String!` - グループのドメイン

**戻り値**: `NodeStatus!`
```graphql
{
nodeId: ID!
groupId: ID!
domain: String!
data: [SensorData!]!
timestamp: AWSDateTime!
}
```

**使用例**:
```graphql
subscription {
onDataUpdateInGroup(groupId: "group-123", domain: "example.com") {
nodeId
groupId
data {
key
value
}
timestamp
}
}
```

---

### onBatchEventInGroup
### onMessageInGroup

**目的**: 複数イベントを一度に送信(1回の Subscription を発火)
**目的**: グループ内のすべてのメッセージ(データ更新、イベント、解散通知)を統合して購読

**トリガー**: `fireEventsByNode` mutation
**トリガー**: `reportDataByNode`, `fireEventsByNode`, `dissolveGroup` mutation

**パラメータ**:
- `groupId: ID!` - 購読するグループ ID
- `domain: String!` - グループのドメイン

**戻り値**: `BatchEvent!`
**戻り値**: `MeshMessage!`
```graphql
{
events: [Event!]!
firedByNodeId: ID!
groupId: ID!
domain: String!
timestamp: AWSDateTime!
groupId: ID! # Subscription フィルタリング用
domain: String! # Subscription フィルタリング用
nodeStatus: NodeStatus # reportDataByNode からのデータ更新
batchEvent: BatchEvent # fireEventsByNode からのイベント
groupDissolve: GroupDissolvePayload # dissolveGroup からの解散通知
}
```

**使用例**:
```graphql
subscription {
onBatchEventInGroup(groupId: "group-123", domain: "example.com") {
events {
name
onMessageInGroup(groupId: "group-123", domain: "example.com") {
groupId
domain
nodeStatus {
nodeId
groupId
domain
data {
key
value
}
timestamp
}
batchEvent {
events {
name
firedByNodeId
payload
timestamp
}
firedByNodeId
payload
groupId
domain
timestamp
}
groupDissolve {
groupId
domain
message
}
}
}
```

---

### onGroupDissolve
**クライアント実装の注意点**:
- `MeshMessage` は各フィールドがオプショナル(null 可能)です
- 受信したメッセージのどのフィールドが設定されているかを確認して、適切に処理してください
- 例: `nodeStatus` が設定されていればデータ更新、`batchEvent` が設定されていればイベント、`groupDissolve` が設定されていれば解散通知

**目的**: リアルタイムでグループ解散を購読
**JavaScript クライアント実装例**:
```javascript
// Subscription を購読
subscription = client.subscribe({
query: gql`
subscription OnMessageInGroup($groupId: ID!, $domain: String!) {
onMessageInGroup(groupId: $groupId, domain: $domain) {
nodeStatus { nodeId data { key value } }
batchEvent { events { name payload } }
groupDissolve { message }
}
}
`,
variables: { groupId, domain }
});

**トリガー**: `dissolveGroup` mutation
subscription.subscribe({
next: (message) => {
const { nodeStatus, batchEvent, groupDissolve } = message.data.onMessageInGroup;

**パラメータ**:
- `groupId: ID!` - 購読するグループ ID
- `domain: String!` - グループのドメイン
if (nodeStatus) {
// データ更新を処理
console.log('Data update:', nodeStatus);
}

**戻り値**: `GroupDissolvePayload!`
```graphql
{
groupId: ID!
domain: String!
message: String!
}
```
if (batchEvent) {
// イベントを処理
console.log('Batch event:', batchEvent);
}

**使用例**:
```graphql
subscription {
onGroupDissolve(groupId: "group-123", domain: "example.com") {
groupId
domain
message
if (groupDissolve) {
// グループ解散を処理
console.log('Group dissolved:', groupDissolve);
// 切断処理など
}
}
}
});
```

---
Expand All @@ -498,7 +504,7 @@ subscription {
- ✅ @aws_subscribe ディレクティブが正しく定義されている
- ✅ Mutations (reportDataByNode, fireEventsByNode, dissolveGroup) が正しく動作する
- ✅ 複数のグループが適切なフィルタリングで共存できる
- ✅ onGroupDissolve subscription が正しくトリガーされる
- ✅ onMessageInGroup (groupDissolve) が正しくトリガーされる

テストを実行:
```bash
Expand Down Expand Up @@ -695,6 +701,6 @@ AWS AppSync のデフォルトのレート制限が適用されます:

---

**Last Updated**: 2026-01-01
**Last Updated**: 2026-01-03
**Phase**: 3 - Documentation Consolidation
**Status**: ✅ Subscriptions と Error Types を統合(完全な API リファレンスは Phase 4 で追加予定
**Status**: ✅ Subscription を `onMessageInGroup` に統合(Issue smalruby/smalruby3-gui#500 関連
30 changes: 15 additions & 15 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ sequenceDiagram
participant DynamoDB
participant Subscription

Node2->>AppSync: subscribe: onDataUpdateInGroup(groupId, domain)
Node2->>AppSync: subscribe: onMessageInGroup(groupId, domain)
AppSync->>Subscription: WebSocket接続確立

Node1->>AppSync: reportDataByNode(nodeId, groupId, domain, data)
Expand All @@ -149,10 +149,10 @@ sequenceDiagram
alt グループ存在
Resolver->>DynamoDB: PutItem: センサーデータ保存
DynamoDB-->>Resolver: Success
Resolver-->>AppSync: NodeStatus
AppSync->>Subscription: Publish: onDataUpdateInGroup
Subscription-->>Node2: NodeStatus(リアルタイム配信)
AppSync-->>Node1: NodeStatus(レスポンス)
Resolver-->>AppSync: MeshMessage
AppSync->>Subscription: Publish: onMessageInGroup (nodeStatus)
Subscription-->>Node2: MeshMessage(リアルタイム配信)
AppSync-->>Node1: MeshMessage(レスポンス)
else グループなし
Resolver-->>AppSync: GroupNotFound error
AppSync-->>Node1: Error
Expand All @@ -170,7 +170,7 @@ sequenceDiagram
participant DynamoDB
participant Subscription

Node2->>AppSync: subscribe: onBatchEventInGroup(groupId, domain)
Node2->>AppSync: subscribe: onMessageInGroup(groupId, domain)
AppSync->>Subscription: WebSocket接続確立

Node1->>AppSync: fireEventsByNode(nodeId, groupId, domain, events[])
Expand All @@ -179,10 +179,10 @@ sequenceDiagram
alt グループ存在
Resolver->>DynamoDB: BatchWriteItem: イベント保存(最大25件)
DynamoDB-->>Resolver: Success
Resolver-->>AppSync: BatchEvent
AppSync->>Subscription: Publish: onBatchEventInGroup
Subscription-->>Node2: BatchEvent(リアルタイム配信)
AppSync-->>Node1: BatchEvent(レスポンス)
Resolver-->>AppSync: MeshMessage
AppSync->>Subscription: Publish: onMessageInGroup (batchEvent)
Subscription-->>Node2: MeshMessage(リアルタイム配信)
AppSync-->>Node1: MeshMessage(レスポンス)
else グループなし
Resolver-->>AppSync: GroupNotFound error
AppSync-->>Node1: Error
Expand Down Expand Up @@ -237,20 +237,20 @@ sequenceDiagram
participant AppSync
participant Subscription

Client1->>AppSync: subscribe: onDataUpdateInGroup(groupId="A", domain="example.com")
Client1->>AppSync: subscribe: onMessageInGroup(groupId="A", domain="example.com")
AppSync->>Subscription: 登録: groupId=A, domain=example.com

Client2->>AppSync: subscribe: onDataUpdateInGroup(groupId="A", domain="example.com")
Client2->>AppSync: subscribe: onMessageInGroup(groupId="A", domain="example.com")
AppSync->>Subscription: 登録: groupId=A, domain=example.com

Client3->>AppSync: subscribe: onDataUpdateInGroup(groupId="B", domain="example.com")
Client3->>AppSync: subscribe: onMessageInGroup(groupId="B", domain="example.com")
AppSync->>Subscription: 登録: groupId=B, domain=example.com

Note over AppSync: reportDataByNode mutation (groupId="A")
AppSync->>Subscription: Publish to groupId="A"

Subscription-->>Client1: NodeStatus(配信)
Subscription-->>Client2: NodeStatus(配信)
Subscription-->>Client1: MeshMessage(配信)
Subscription-->>Client2: MeshMessage(配信)
Note over Client3: groupId="B"なので配信されない
```

Expand Down
30 changes: 30 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,36 @@ query IntrospectionQuery {
}
```

#### テスト4: 統合された Subscription の購読 (onMessageInGroup)

`wscat` または AppSync Console の **Queries** タブで、統合された Subscription が正しく動作することを確認します。

```graphql
subscription OnMessageInGroup {
onMessageInGroup(groupId: "test-group", domain: "test-domain") {
groupId
domain
nodeStatus {
nodeId
data {
key
value
}
}
batchEvent {
events {
name
}
}
groupDissolve {
message
}
}
}
```

購読した状態で、別のタブから `reportDataByNode` mutation を実行し、データがリアルタイムで届くことを確認してください。

### 7.3 CLIからのテスト

`curl` コマンドでAPIをテストすることもできます。
Expand Down
2 changes: 1 addition & 1 deletion docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ npx cdk deploy --rollback
1. **パラメータ確認**:
```javascript
// Subscription
onDataUpdateInGroup(groupId: "abc123", domain: "192.168.1.1")
onMessageInGroup(groupId: "abc123", domain: "192.168.1.1")

// Mutation (一致する必要あり)
reportDataByNode(groupId: "abc123", domain: "192.168.1.1", ...)
Expand Down
Loading
Loading