@wcstack/websocket 1.8.1

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.ja.md ADDED
@@ -0,0 +1,546 @@
1
+ # @wcstack/websocket
2
+
3
+ `@wcstack/websocket` は wcstack エコシステムのためのヘッドレス WebSocket コンポーネントです。
4
+
5
+ 視覚的な UI ウィジェットではありません。
6
+ WebSocket 通信とリアクティブな状態をつなぐ **I/O ノード** です。
7
+
8
+ `@wcstack/state` と組み合わせると、`<wcs-ws>` はパス契約を通じて直接バインドできます:
9
+
10
+ - **入力 / コマンドサーフェス**: `url`, `trigger`, `send`
11
+ - **出力ステートサーフェス**: `message`, `connected`, `loading`, `error`, `readyState`
12
+
13
+ つまり、リアルタイム通信を HTML 内で宣言的に表現できます。UI レイヤーに `new WebSocket()`、`onmessage`、接続管理のグルーコードを書く必要はありません。
14
+
15
+ `@wcstack/websocket` は [HAWC](https://github.com/wc-bindable-protocol/wc-bindable-protocol/blob/main/docs/articles/HAWC.md) アーキテクチャに従います:
16
+
17
+ - **Core** (`WebSocketCore`) が接続、メッセージング、再接続、非同期状態を処理
18
+ - **Shell** (`<wcs-ws>`) がその状態を DOM に接続
19
+ - フレームワークやバインディングシステムは [wc-bindable-protocol](https://github.com/wc-bindable-protocol/wc-bindable-protocol) 経由で利用
20
+
21
+ ## なぜこれが存在するのか
22
+
23
+ リアルタイム機能には通常、命令的な WebSocket 管理が必要です。接続ライフサイクル、再接続ロジック、メッセージのパース、エラー処理、切断時のクリーンアップ。
24
+
25
+ `@wcstack/websocket` はそのロジックを再利用可能なコンポーネントに移し、結果をバインド可能な状態として公開します。
26
+
27
+ `@wcstack/state` と組み合わせたフローは:
28
+
29
+ 1. 状態が `url` を決定(または `trigger` が発火)
30
+ 2. `<wcs-ws>` が接続を開く
31
+ 3. 受信メッセージが `message` として、接続状態が `connected`、`loading`、`error` として返る
32
+ 4. UI は `data-wcs` でそれらのパスにバインド
33
+
34
+ リアルタイム通信が命令的なイベント配線ではなく、**状態遷移**になります。
35
+
36
+ ## インストール
37
+
38
+ ```bash
39
+ npm install @wcstack/websocket
40
+ ```
41
+
42
+ ## クイックスタート
43
+
44
+ ### 1. 状態からのリアクティブ WebSocket
45
+
46
+ `<wcs-ws>` が DOM に接続され `url` が設定されると、自動的に WebSocket 接続を開きます。JSON メッセージは自動パースされます。
47
+
48
+ ```html
49
+ <script type="module" src="https://esm.run/@wcstack/state/auto"></script>
50
+ <script type="module" src="https://esm.run/@wcstack/websocket/auto"></script>
51
+
52
+ <wcs-state>
53
+ <script type="application/json">
54
+ {
55
+ "lastMessage": null,
56
+ "isConnected": false,
57
+ "isLoading": false
58
+ }
59
+ </script>
60
+
61
+ <wcs-ws
62
+ url="wss://example.com/ws"
63
+ data-wcs="message: lastMessage; connected: isConnected; loading: isLoading">
64
+ </wcs-ws>
65
+
66
+ <p data-wcs="textContent: isConnected|then('接続中','切断')"></p>
67
+ <pre data-wcs="textContent: lastMessage|json"></pre>
68
+ </wcs-state>
69
+ ```
70
+
71
+ これがデフォルトモードです:
72
+
73
+ - `url` を設定
74
+ - `message` を受け取る
75
+ - 任意で `connected`、`loading`、`error`、`readyState` もバインド
76
+
77
+ ### 2. 状態からのメッセージ送信
78
+
79
+ `send` プロパティでサーバーにデータを送信します。`send` に値を設定すると即時送信され、オブジェクトは自動的に JSON 文字列化されます。
80
+
81
+ ```html
82
+ <wcs-state>
83
+ <script type="module">
84
+ export default {
85
+ chatInput: "",
86
+ lastMessage: null,
87
+ outgoing: null,
88
+
89
+ sendChat() {
90
+ this.outgoing = { type: "chat", content: this.chatInput };
91
+ this.chatInput = "";
92
+ },
93
+ };
94
+ </script>
95
+
96
+ <wcs-ws
97
+ url="wss://example.com/ws"
98
+ data-wcs="message: lastMessage; send: outgoing">
99
+ </wcs-ws>
100
+
101
+ <input data-wcs="value: chatInput" placeholder="メッセージを入力">
102
+ <button data-wcs="onclick: sendChat">送信</button>
103
+
104
+ <pre data-wcs="textContent: lastMessage|json"></pre>
105
+ </wcs-state>
106
+ ```
107
+
108
+ ### 3. `trigger` による手動接続
109
+
110
+ 接続タイミングを制御したい場合は `manual` を使います。
111
+
112
+ ```html
113
+ <wcs-state>
114
+ <script type="module">
115
+ export default {
116
+ shouldConnect: false,
117
+ lastMessage: null,
118
+ isConnected: false,
119
+
120
+ openConnection() {
121
+ this.shouldConnect = true;
122
+ },
123
+ };
124
+ </script>
125
+
126
+ <wcs-ws
127
+ url="wss://example.com/ws"
128
+ manual
129
+ data-wcs="trigger: shouldConnect; message: lastMessage; connected: isConnected">
130
+ </wcs-ws>
131
+
132
+ <button data-wcs="onclick: openConnection">接続</button>
133
+ <p data-wcs="textContent: isConnected|then('接続中','切断')"></p>
134
+ </wcs-state>
135
+ ```
136
+
137
+ `trigger` は **単方向のコマンドサーフェス** です:
138
+
139
+ - `true` を書き込むと接続を開始
140
+ - 接続開始後に自動で `false` にリセット
141
+ - リセット時に `wcs-ws:trigger-changed` を発火
142
+
143
+ ```
144
+ 外部からの書き込み: false → true イベントなし(接続を開始)
145
+ 自動リセット: true → false wcs-ws:trigger-changed を発火
146
+ ```
147
+
148
+ ### 4. 自動再接続
149
+
150
+ ```html
151
+ <wcs-ws
152
+ url="wss://example.com/ws"
153
+ auto-reconnect
154
+ reconnect-interval="5000"
155
+ max-reconnects="10"
156
+ data-wcs="message: lastMessage; connected: isConnected; error: wsError">
157
+ </wcs-ws>
158
+ ```
159
+
160
+ 接続が異常切断された場合(クローズコード 1000 以外)、`<wcs-ws>` は自動的に再接続します:
161
+
162
+ - `reconnect-interval` ミリ秒待機(デフォルト: 3000)
163
+ - 最大 `max-reconnects` 回リトライ(デフォルト: Infinity)
164
+ - 再接続成功時にリトライカウントをリセット
165
+
166
+ ## ステートサーフェス vs コマンドサーフェス
167
+
168
+ `<wcs-ws>` は 2 種類のプロパティを公開します。
169
+
170
+ ### 出力ステート(バインド可能な非同期状態)
171
+
172
+ 現在の接続状態を表し、HAWC のメインサーフェスです:
173
+
174
+ | プロパティ | 型 | 説明 |
175
+ |------------|------|------|
176
+ | `message` | `any` | 最新の受信メッセージ(JSON 自動パース) |
177
+ | `connected` | `boolean` | WebSocket 接続中は `true` |
178
+ | `loading` | `boolean` | 接続処理中は `true` |
179
+ | `error` | `WcsWsError \| Event \| null` | 接続またはクローズエラー |
180
+ | `readyState` | `number` | WebSocket readyState 定数 |
181
+
182
+ ### 入力 / コマンドサーフェス
183
+
184
+ HTML、JS、または `@wcstack/state` バインディングから接続とメッセージングを制御します:
185
+
186
+ | プロパティ | 型 | 説明 |
187
+ |------------|------|------|
188
+ | `url` | `string` | WebSocket エンドポイント URL |
189
+ | `trigger` | `boolean` | 単方向の接続トリガー |
190
+ | `send` | `any` | 値を設定するとデータを送信(オブジェクトは自動文字列化) |
191
+ | `manual` | `boolean` | DOM 接続時の自動接続を無効化 |
192
+
193
+ ## アーキテクチャ
194
+
195
+ `@wcstack/websocket` は HAWC アーキテクチャに従います。
196
+
197
+ ### Core: `WebSocketCore`
198
+
199
+ `WebSocketCore` は純粋な `EventTarget` クラスです。
200
+ 以下を内包します:
201
+
202
+ - WebSocket 接続管理
203
+ - 自動再接続ロジック
204
+ - JSON メッセージパース
205
+ - 非同期状態遷移
206
+ - `wc-bindable-protocol` 宣言
207
+
208
+ `EventTarget` と `WebSocket` をサポートする任意のランタイムでヘッドレスに動作します。
209
+
210
+ ### Shell: `<wcs-ws>`
211
+
212
+ `<wcs-ws>` は `WebSocketCore` の薄い `HTMLElement` ラッパーです。
213
+ 以下を追加します:
214
+
215
+ - 属性 / プロパティマッピング
216
+ - DOM ライフサイクル統合
217
+ - `trigger`、`send` などの宣言的ヘルパー
218
+
219
+ この分離により、接続ロジックのポータビリティを保ちながら、`@wcstack/state` のような DOM ベースのバインディングシステムとの自然な連携を可能にしています。
220
+
221
+ ### Target injection
222
+
223
+ Core は **target injection** により Shell 上で直接イベントを発火するため、イベントの再ディスパッチは不要です。
224
+
225
+ ## ヘッドレス利用(Core 単体)
226
+
227
+ `WebSocketCore` は DOM なしで単体利用できます。`static wcBindable` を宣言しているため、`@wc-bindable/core` の `bind()` で状態をサブスクライブできます:
228
+
229
+ ```typescript
230
+ import { WebSocketCore } from "@wcstack/websocket";
231
+ import { bind } from "@wc-bindable/core";
232
+
233
+ const core = new WebSocketCore();
234
+
235
+ const unbind = bind(core, (name, value) => {
236
+ console.log(`${name}:`, value);
237
+ });
238
+
239
+ core.connect("wss://example.com/ws", {
240
+ autoReconnect: true,
241
+ reconnectInterval: 5000,
242
+ });
243
+
244
+ // メッセージ送信
245
+ core.send(JSON.stringify({ type: "ping" }));
246
+
247
+ // クリーンアップ
248
+ core.close();
249
+ unbind();
250
+ ```
251
+
252
+ Node.js、Deno、Cloudflare Workers など、`EventTarget` と `WebSocket` が利用可能な環境で動作します。
253
+
254
+ ## URL の監視
255
+
256
+ `<wcs-ws>` はデフォルトで以下のタイミングに自動的に接続を開きます:
257
+
258
+ 1. DOM に接続され、`url` が設定されているとき
259
+ 2. DOM 接続中に `url` 属性が変更されたとき
260
+
261
+ `manual` 属性を設定すると自動接続が無効になり、`connect()` メソッドや `trigger` プロパティで明示的に制御できます。
262
+
263
+ ## プログラムからの利用
264
+
265
+ ```javascript
266
+ const wsEl = document.querySelector("wcs-ws");
267
+
268
+ // 手動接続
269
+ wsEl.connect();
270
+
271
+ // データ送信
272
+ wsEl.sendMessage(JSON.stringify({ type: "chat", content: "こんにちは" }));
273
+
274
+ console.log(wsEl.message); // 最新メッセージ
275
+ console.log(wsEl.connected); // boolean
276
+ console.log(wsEl.loading); // boolean
277
+ console.log(wsEl.error); // エラー情報 or null
278
+ console.log(wsEl.readyState); // WebSocket readyState
279
+
280
+ // 切断
281
+ wsEl.close();
282
+ ```
283
+
284
+ ## オプションの DOM トリガー
285
+
286
+ `autoTrigger` が有効(デフォルト)の場合、`data-wstarget` 属性を持つ要素のクリックで対応する `<wcs-ws>` の接続が実行されます:
287
+
288
+ ```html
289
+ <button data-wstarget="my-ws">接続</button>
290
+ <wcs-ws id="my-ws" url="wss://example.com/ws" manual></wcs-ws>
291
+ ```
292
+
293
+ イベント委譲を使用しているため、動的に追加された要素でも動作します。`closest()` API により、ネストされた子要素(ボタン内のアイコン等)のクリックも検出します。
294
+
295
+ 指定した id に一致する要素が存在しない場合、または一致した要素が `<wcs-ws>` でない場合、クリックは無視されます(エラーは発生しません)。
296
+
297
+ これは便利機能です。
298
+ wcstack アプリケーションでは、**`trigger` によるステート駆動のトリガー**が通常の主要パターンです。
299
+
300
+ ## 要素一覧
301
+
302
+ ### `<wcs-ws>`
303
+
304
+ | 属性 | 型 | デフォルト | 説明 |
305
+ |------|------|------------|------|
306
+ | `url` | `string` | — | WebSocket エンドポイント URL |
307
+ | `protocols` | `string` | — | カンマ区切りのサブプロトコルリスト |
308
+ | `manual` | `boolean` | `false` | 自動接続を無効化 |
309
+ | `auto-reconnect` | `boolean` | `false` | 自動再接続を有効化 |
310
+ | `reconnect-interval` | `number` | `3000` | 再接続間隔(ミリ秒) |
311
+ | `max-reconnects` | `number` | `Infinity` | 最大再接続回数 |
312
+
313
+ | プロパティ | 型 | 説明 |
314
+ |------------|------|------|
315
+ | `message` | `any` | 最新の受信メッセージ(JSON 自動パース) |
316
+ | `connected` | `boolean` | WebSocket 接続中は `true` |
317
+ | `loading` | `boolean` | 接続処理中は `true` |
318
+ | `error` | `WcsWsError \| Event \| null` | エラー情報 |
319
+ | `readyState` | `number` | WebSocket readyState 定数 |
320
+ | `trigger` | `boolean` | `true` を設定すると接続を開始 |
321
+ | `send` | `any` | 値を設定するとデータを送信 |
322
+
323
+ | メソッド | 説明 |
324
+ |----------|------|
325
+ | `connect()` | WebSocket 接続を開く |
326
+ | `sendMessage(data)` | 接続経由でデータを送信 |
327
+ | `close(code?, reason?)` | 接続を閉じる |
328
+
329
+ ## wc-bindable-protocol
330
+
331
+ `WebSocketCore` と `<wcs-ws>` はどちらも wc-bindable-protocol に準拠しており、プロトコル対応の任意のフレームワークやコンポーネントと相互運用できます。
332
+
333
+ ### Core (`WebSocketCore`)
334
+
335
+ `WebSocketCore` は任意のランタイムからサブスクライブできるバインド可能な非同期状態を宣言します:
336
+
337
+ ```typescript
338
+ static wcBindable = {
339
+ protocol: "wc-bindable",
340
+ version: 1,
341
+ properties: [
342
+ { name: "message", event: "wcs-ws:message" },
343
+ { name: "connected", event: "wcs-ws:connected-changed" },
344
+ { name: "loading", event: "wcs-ws:loading-changed" },
345
+ { name: "error", event: "wcs-ws:error" },
346
+ { name: "readyState", event: "wcs-ws:readystate-changed" },
347
+ ],
348
+ };
349
+ ```
350
+
351
+ ヘッドレスの利用者は `core.connect(url)` を直接呼ぶため、`trigger` は不要です。
352
+
353
+ ### Shell (`<wcs-ws>`)
354
+
355
+ Shell は Core の宣言を拡張し、バインディングシステムから宣言的に接続とメッセージングを制御できるようにします:
356
+
357
+ ```typescript
358
+ static wcBindable = {
359
+ ...WebSocketCore.wcBindable,
360
+ properties: [
361
+ ...WebSocketCore.wcBindable.properties,
362
+ { name: "trigger", event: "wcs-ws:trigger-changed" },
363
+ { name: "send", event: "wcs-ws:send-changed" },
364
+ ],
365
+ };
366
+ ```
367
+
368
+ ## TypeScript 型
369
+
370
+ ```typescript
371
+ import type {
372
+ WcsWsError, WcsWsCoreValues, WcsWsValues
373
+ } from "@wcstack/websocket";
374
+ ```
375
+
376
+ ```typescript
377
+ // WebSocket エラー
378
+ interface WcsWsError {
379
+ code?: number;
380
+ reason?: string;
381
+ message?: string;
382
+ }
383
+
384
+ // Core(ヘッドレス)— 5 つの非同期状態プロパティ
385
+ // T のデフォルトは unknown。型引数を渡すと message が型付けされる
386
+ interface WcsWsCoreValues<T = unknown> {
387
+ message: T;
388
+ connected: boolean;
389
+ loading: boolean;
390
+ error: WcsWsError | Event | null;
391
+ readyState: number;
392
+ }
393
+
394
+ // Shell(<wcs-ws>)— Core を拡張し trigger と send を追加
395
+ interface WcsWsValues<T = unknown> extends WcsWsCoreValues<T> {
396
+ trigger: boolean;
397
+ send: unknown;
398
+ }
399
+ ```
400
+
401
+ ## なぜ `@wcstack/state` とうまく連携するのか
402
+
403
+ `@wcstack/state` は UI と状態の唯一の契約としてパス文字列を使います。
404
+ `<wcs-ws>` はこのモデルに自然に適合します:
405
+
406
+ - 状態が `url` を決定、または `trigger` を発火
407
+ - `<wcs-ws>` が接続を開いて管理
408
+ - 受信データが `message` として、ステータスが `connected`、`loading`、`error` として返る
409
+ - UI は WebSocket のグルーコードを書かずにそれらのパスにバインド
410
+ - 送信データは `send` プロパティ経由で流れる
411
+
412
+ リアルタイム通信が通常の状態更新と同じように見えるようになります。
413
+
414
+ ## フレームワーク連携
415
+
416
+ `<wcs-ws>` は HAWC + `wc-bindable-protocol` なので、`@wc-bindable/*` の薄いアダプタを通じて任意のフレームワークで動作します。
417
+
418
+ ### React
419
+
420
+ ```tsx
421
+ import { useWcBindable } from "@wc-bindable/react";
422
+ import type { WcsWsValues } from "@wcstack/websocket";
423
+
424
+ interface ChatMessage { type: string; content: string; }
425
+
426
+ function Chat() {
427
+ const [ref, { message, connected, loading }] =
428
+ useWcBindable<HTMLElement, WcsWsValues<ChatMessage>>();
429
+
430
+ return (
431
+ <>
432
+ <wcs-ws ref={ref} url="wss://example.com/ws" auto-reconnect />
433
+ {loading && <p>接続中...</p>}
434
+ {connected && <p>接続済み</p>}
435
+ {message && <pre>{JSON.stringify(message)}</pre>}
436
+ </>
437
+ );
438
+ }
439
+ ```
440
+
441
+ ### Vue
442
+
443
+ ```vue
444
+ <script setup lang="ts">
445
+ import { useWcBindable } from "@wc-bindable/vue";
446
+ import type { WcsWsValues } from "@wcstack/websocket";
447
+
448
+ interface ChatMessage { type: string; content: string; }
449
+
450
+ const { ref, values } = useWcBindable<HTMLElement, WcsWsValues<ChatMessage>>();
451
+ </script>
452
+
453
+ <template>
454
+ <wcs-ws :ref="ref" url="wss://example.com/ws" auto-reconnect />
455
+ <p v-if="values.loading">接続中...</p>
456
+ <p v-else-if="values.connected">接続済み</p>
457
+ <pre v-if="values.message">{{ values.message }}</pre>
458
+ </template>
459
+ ```
460
+
461
+ ### Svelte
462
+
463
+ ```svelte
464
+ <script>
465
+ import { wcBindable } from "@wc-bindable/svelte";
466
+
467
+ let message = $state(null);
468
+ let connected = $state(false);
469
+ </script>
470
+
471
+ <wcs-ws url="wss://example.com/ws" auto-reconnect
472
+ use:wcBindable={{ onUpdate: (name, v) => {
473
+ if (name === "message") message = v;
474
+ if (name === "connected") connected = v;
475
+ }}} />
476
+
477
+ <p>{connected ? "接続済み" : "切断"}</p>
478
+ {#if message}
479
+ <pre>{JSON.stringify(message)}</pre>
480
+ {/if}
481
+ ```
482
+
483
+ ### Solid
484
+
485
+ ```tsx
486
+ import { createWcBindable } from "@wc-bindable/solid";
487
+ import type { WcsWsValues } from "@wcstack/websocket";
488
+
489
+ interface ChatMessage { type: string; content: string; }
490
+
491
+ function Chat() {
492
+ const [values, directive] = createWcBindable<WcsWsValues<ChatMessage>>();
493
+
494
+ return (
495
+ <>
496
+ <wcs-ws ref={directive} url="wss://example.com/ws" auto-reconnect />
497
+ <Show when={values.connected} fallback={<p>切断</p>}>
498
+ <p>接続済み</p>
499
+ </Show>
500
+ <Show when={values.message}>
501
+ <pre>{JSON.stringify(values.message)}</pre>
502
+ </Show>
503
+ </>
504
+ );
505
+ }
506
+ ```
507
+
508
+ ### Vanilla — `bind()` を直接利用
509
+
510
+ ```javascript
511
+ import { bind } from "@wc-bindable/core";
512
+
513
+ const wsEl = document.querySelector("wcs-ws");
514
+
515
+ bind(wsEl, (name, value) => {
516
+ console.log(`${name} changed:`, value);
517
+ });
518
+ ```
519
+
520
+ ## 設定
521
+
522
+ ```javascript
523
+ import { bootstrapWebSocket } from "@wcstack/websocket";
524
+
525
+ bootstrapWebSocket({
526
+ autoTrigger: true,
527
+ triggerAttribute: "data-wstarget",
528
+ tagNames: {
529
+ ws: "wcs-ws",
530
+ },
531
+ });
532
+ ```
533
+
534
+ ## 設計メモ
535
+
536
+ - `message`、`connected`、`loading`、`error`、`readyState` は **出力ステート**
537
+ - `url`、`trigger`、`send` は **入力 / コマンドサーフェス**
538
+ - `trigger` は意図的に単方向: `true` を書き込むと接続、リセットで完了を通知
539
+ - `send` は即時送信後に `null` にリセット — 送信のたびに値を設定する
540
+ - JSON メッセージは受信時に自動パース、オブジェクトは送信時に自動文字列化
541
+ - `manual` は接続タイミングを明示的に制御したい場合に有用
542
+ - 自動再接続は異常切断時のみ発動(コード 1000 以外)
543
+
544
+ ## ライセンス
545
+
546
+ MIT