@uzuhq/code-sdk 0.7.5 → 0.7.7

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 (50) hide show
  1. package/dist/dev-globals.d.ts +12 -27
  2. package/dist/dev-globals.js +0 -15
  3. package/dist/dev-hooks-ClWM8HzI.d.ts +682 -0
  4. package/dist/index.d.ts +152 -66
  5. package/dist/index.js +1837 -414
  6. package/package.json +8 -5
  7. package/dist/action-types.test-d.d.ts +0 -11
  8. package/dist/action-types.test-d.js +0 -101
  9. package/dist/dev-hooks.d.ts +0 -241
  10. package/dist/dev-hooks.js +0 -132
  11. package/dist/dev-hooks.test.d.ts +0 -1
  12. package/dist/dev-hooks.test.js +0 -294
  13. package/dist/dev-prediction-traps.d.ts +0 -32
  14. package/dist/dev-prediction-traps.js +0 -0
  15. package/dist/dev-prediction-traps.test.d.ts +0 -1
  16. package/dist/dev-prediction-traps.test.js +0 -178
  17. package/dist/dev-state-patch.d.ts +0 -81
  18. package/dist/dev-state-patch.js +0 -295
  19. package/dist/dev-state-patch.test.d.ts +0 -1
  20. package/dist/dev-state-patch.test.js +0 -333
  21. package/dist/json-patch.d.ts +0 -7
  22. package/dist/json-patch.js +0 -78
  23. package/dist/random.d.ts +0 -11
  24. package/dist/random.js +0 -34
  25. package/dist/reconnectable-ws.d.ts +0 -60
  26. package/dist/reconnectable-ws.js +0 -229
  27. package/dist/room.d.ts +0 -23
  28. package/dist/room.js +0 -36
  29. package/dist/run/local-server-action.d.ts +0 -16
  30. package/dist/run/local-server-action.js +0 -217
  31. package/dist/run/local-server-action.test.d.ts +0 -1
  32. package/dist/run/local-server-action.test.js +0 -242
  33. package/dist/run/optimistic-action-client.d.ts +0 -68
  34. package/dist/run/optimistic-action-client.js +0 -209
  35. package/dist/run/optimistic-action-client.test.d.ts +0 -1
  36. package/dist/run/optimistic-action-client.test.js +0 -430
  37. package/dist/run/server-action.d.ts +0 -16
  38. package/dist/run/server-action.js +0 -177
  39. package/dist/run/server-action.test.d.ts +0 -1
  40. package/dist/run/server-action.test.js +0 -96
  41. package/dist/server-clock.d.ts +0 -29
  42. package/dist/server-clock.js +0 -40
  43. package/dist/server-only.d.ts +0 -33
  44. package/dist/server-only.js +0 -21
  45. package/dist/sync/local.d.ts +0 -8
  46. package/dist/sync/local.js +0 -50
  47. package/dist/sync/online.d.ts +0 -5
  48. package/dist/sync/online.js +0 -165
  49. package/dist/types.d.ts +0 -345
  50. package/dist/types.js +0 -8
@@ -1,78 +0,0 @@
1
- function escapePointer(key) {
2
- return key.replace(/~/g, '~0').replace(/\//g, '~1');
3
- }
4
- function unescapePointer(token) {
5
- return token.replace(/~1/g, '/').replace(/~0/g, '~');
6
- }
7
- export function compare(oldObj, newObj, basePath = '') {
8
- if (oldObj === newObj)
9
- return [];
10
- // Handle nulls / primitives
11
- if (oldObj === null ||
12
- newObj === null ||
13
- typeof oldObj !== 'object' ||
14
- typeof newObj !== 'object') {
15
- return [{ op: 'replace', path: basePath || '/', value: newObj }];
16
- }
17
- // Arrays: compare as whole (no element-level diff)
18
- if (Array.isArray(oldObj) || Array.isArray(newObj)) {
19
- if (JSON.stringify(oldObj) === JSON.stringify(newObj))
20
- return [];
21
- return [{ op: 'replace', path: basePath || '/', value: newObj }];
22
- }
23
- // Objects: recurse
24
- const ops = [];
25
- const oldKeys = Object.keys(oldObj);
26
- const newKeys = Object.keys(newObj);
27
- // Removed keys
28
- for (const key of oldKeys) {
29
- if (!(key in newObj)) {
30
- ops.push({ op: 'remove', path: `${basePath}/${escapePointer(key)}` });
31
- }
32
- }
33
- // Added or changed keys
34
- for (const key of newKeys) {
35
- const childPath = `${basePath}/${escapePointer(key)}`;
36
- if (!(key in oldObj)) {
37
- ops.push({ op: 'add', path: childPath, value: newObj[key] });
38
- }
39
- else {
40
- const childOps = compare(oldObj[key], newObj[key], childPath);
41
- ops.push(...childOps);
42
- }
43
- }
44
- return ops;
45
- }
46
- export function applyPatch(doc, ops) {
47
- for (const op of ops) {
48
- const tokens = op.path.split('/').slice(1).map(unescapePointer);
49
- if (tokens.length === 0)
50
- return false;
51
- if (op.op === 'replace' || op.op === 'add') {
52
- let target = doc;
53
- for (let i = 0; i < tokens.length - 1; i++) {
54
- target = target?.[tokens[i]];
55
- if (target === undefined || target === null)
56
- return false;
57
- }
58
- const lastKey = tokens[tokens.length - 1];
59
- target[lastKey] = op.value;
60
- }
61
- else if (op.op === 'remove') {
62
- let target = doc;
63
- for (let i = 0; i < tokens.length - 1; i++) {
64
- target = target?.[tokens[i]];
65
- if (target === undefined || target === null)
66
- return false;
67
- }
68
- const lastKey = tokens[tokens.length - 1];
69
- if (Array.isArray(target)) {
70
- target.splice(Number(lastKey), 1);
71
- }
72
- else {
73
- delete target[lastKey];
74
- }
75
- }
76
- }
77
- return true;
78
- }
package/dist/random.d.ts DELETED
@@ -1,11 +0,0 @@
1
- import type { SeededRandom } from './types.js';
2
- export declare class SeededRandomImpl implements SeededRandom {
3
- private _state;
4
- constructor(seed: number);
5
- get state(): number;
6
- static fromState(state: number): SeededRandomImpl;
7
- float(): number;
8
- int(max: number): number;
9
- pick<T>(array: T[]): T;
10
- shuffle<T>(array: T[]): T[];
11
- }
package/dist/random.js DELETED
@@ -1,34 +0,0 @@
1
- export class SeededRandomImpl {
2
- constructor(seed) {
3
- this._state = seed | 0;
4
- }
5
- get state() {
6
- return this._state;
7
- }
8
- static fromState(state) {
9
- const r = new SeededRandomImpl(0);
10
- r._state = state;
11
- return r;
12
- }
13
- float() {
14
- this._state |= 0;
15
- this._state = (this._state + 0x6d2b79f5) | 0;
16
- let t = Math.imul(this._state ^ (this._state >>> 15), 1 | this._state);
17
- t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
18
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
19
- }
20
- int(max) {
21
- return Math.floor(this.float() * max);
22
- }
23
- pick(array) {
24
- return array[this.int(array.length)];
25
- }
26
- shuffle(array) {
27
- const a = [...array];
28
- for (let i = a.length - 1; i > 0; i--) {
29
- const j = this.int(i + 1);
30
- [a[i], a[j]] = [a[j], a[i]];
31
- }
32
- return a;
33
- }
34
- }
@@ -1,60 +0,0 @@
1
- /**
2
- * 自動再接続 WebSocket ラッパー
3
- *
4
- * - Exponential backoff + jitter 付き自動再接続
5
- * - Heartbeat (__ping / __pong) による接続生存確認
6
- * - 再接続中のメッセージバッファリング
7
- */
8
- export type ConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
9
- export interface ReconnectableWSOptions {
10
- /** 最大再接続試行回数 (default: 15) */
11
- maxReconnectAttempts?: number;
12
- /** Backoff の基準遅延 ms (default: 1000) */
13
- baseDelay?: number;
14
- /** Backoff の最大遅延 ms (default: 30000) */
15
- maxDelay?: number;
16
- /** Jitter 係数 0〜1 (default: 0.5) */
17
- jitterFactor?: number;
18
- /** Heartbeat 送信間隔 ms (default: 25000) */
19
- heartbeatInterval?: number;
20
- /** Pong 待ちタイムアウト ms (default: 10000) */
21
- heartbeatTimeout?: number;
22
- /** 再接続中のバッファ上限 (default: 50) */
23
- maxBufferSize?: number;
24
- /** バッファ対象判定。false を返すとドロップ */
25
- shouldBuffer?: (data: string) => boolean;
26
- /** 接続状態変更コールバック */
27
- onConnectionStateChange?: (state: ConnectionState) => void;
28
- }
29
- export declare class ReconnectableWebSocket {
30
- private ws;
31
- private readonly url;
32
- private readonly opts;
33
- private _state;
34
- private reconnectAttempt;
35
- private messageBuffer;
36
- private messageHandlers;
37
- private heartbeatTimer;
38
- private heartbeatTimeoutTimer;
39
- private reconnectTimer;
40
- private disposed;
41
- constructor(url: string, options?: ReconnectableWSOptions);
42
- get connectionState(): ConnectionState;
43
- /** WebSocket.readyState 互換 (既存コードとの互換用) */
44
- get readyState(): number;
45
- send(data: string): void;
46
- addEventListener(type: 'message', handler: (ev: MessageEvent) => void): void;
47
- removeEventListener(type: 'message', handler: (ev: MessageEvent) => void): void;
48
- /** バッファをクリア(権威的 state 受信時に呼ぶ) */
49
- clearBuffer(): void;
50
- /** 全リソース解放 */
51
- dispose(): void;
52
- private connect;
53
- private setState;
54
- private scheduleReconnect;
55
- private getReconnectDelay;
56
- private flushBuffer;
57
- private startHeartbeat;
58
- private stopHeartbeat;
59
- private handlePong;
60
- }
@@ -1,229 +0,0 @@
1
- /**
2
- * 自動再接続 WebSocket ラッパー
3
- *
4
- * - Exponential backoff + jitter 付き自動再接続
5
- * - Heartbeat (__ping / __pong) による接続生存確認
6
- * - 再接続中のメッセージバッファリング
7
- */
8
- const DEFAULTS = {
9
- maxReconnectAttempts: 15,
10
- baseDelay: 1000,
11
- maxDelay: 30000,
12
- jitterFactor: 0.5,
13
- heartbeatInterval: 25000,
14
- heartbeatTimeout: 10000,
15
- maxBufferSize: 50,
16
- };
17
- export class ReconnectableWebSocket {
18
- constructor(url, options = {}) {
19
- this.ws = null;
20
- this._state = 'connecting';
21
- this.reconnectAttempt = 0;
22
- this.messageBuffer = [];
23
- this.messageHandlers = [];
24
- this.heartbeatTimer = null;
25
- this.heartbeatTimeoutTimer = null;
26
- this.reconnectTimer = null;
27
- this.disposed = false;
28
- this.url = url;
29
- this.opts = {
30
- maxReconnectAttempts: options.maxReconnectAttempts ?? DEFAULTS.maxReconnectAttempts,
31
- baseDelay: options.baseDelay ?? DEFAULTS.baseDelay,
32
- maxDelay: options.maxDelay ?? DEFAULTS.maxDelay,
33
- jitterFactor: options.jitterFactor ?? DEFAULTS.jitterFactor,
34
- heartbeatInterval: options.heartbeatInterval ?? DEFAULTS.heartbeatInterval,
35
- heartbeatTimeout: options.heartbeatTimeout ?? DEFAULTS.heartbeatTimeout,
36
- maxBufferSize: options.maxBufferSize ?? DEFAULTS.maxBufferSize,
37
- shouldBuffer: options.shouldBuffer,
38
- onConnectionStateChange: options.onConnectionStateChange,
39
- };
40
- this.connect();
41
- }
42
- // ─── Public API ──────────────────────────────────────────
43
- get connectionState() {
44
- return this._state;
45
- }
46
- /** WebSocket.readyState 互換 (既存コードとの互換用) */
47
- get readyState() {
48
- return this.ws?.readyState ?? WebSocket.CLOSED;
49
- }
50
- send(data) {
51
- if (this._state === 'connected' && this.ws?.readyState === WebSocket.OPEN) {
52
- this.ws.send(data);
53
- }
54
- else if (this._state === 'reconnecting') {
55
- if (this.opts.shouldBuffer && !this.opts.shouldBuffer(data))
56
- return;
57
- if (this.messageBuffer.length < this.opts.maxBufferSize) {
58
- this.messageBuffer.push(data);
59
- }
60
- }
61
- // disconnected / connecting: ドロップ
62
- }
63
- addEventListener(type, handler) {
64
- if (type === 'message') {
65
- this.messageHandlers.push(handler);
66
- }
67
- }
68
- removeEventListener(type, handler) {
69
- if (type === 'message') {
70
- const index = this.messageHandlers.indexOf(handler);
71
- if (index !== -1) {
72
- this.messageHandlers.splice(index, 1);
73
- }
74
- }
75
- }
76
- /** バッファをクリア(権威的 state 受信時に呼ぶ) */
77
- clearBuffer() {
78
- this.messageBuffer = [];
79
- }
80
- /** 全リソース解放 */
81
- dispose() {
82
- this.disposed = true;
83
- this.stopHeartbeat();
84
- if (this.reconnectTimer) {
85
- clearTimeout(this.reconnectTimer);
86
- this.reconnectTimer = null;
87
- }
88
- if (this.ws) {
89
- this.ws.onopen = null;
90
- this.ws.onclose = null;
91
- this.ws.onerror = null;
92
- this.ws.onmessage = null;
93
- try {
94
- if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
95
- this.ws.close();
96
- }
97
- }
98
- catch (e) {
99
- console.warn('[ReconnectableWS] Error closing WebSocket:', e);
100
- }
101
- this.ws = null;
102
- }
103
- }
104
- // ─── Private ─────────────────────────────────────────────
105
- connect() {
106
- if (this.disposed)
107
- return;
108
- console.log(`[ReconnectableWS] 🔗 Connecting to ${this.url} (attempt=${this.reconnectAttempt})`);
109
- const ws = new WebSocket(this.url);
110
- ws.onopen = () => {
111
- if (this.disposed)
112
- return;
113
- console.log(`[ReconnectableWS] ✅ Connected`);
114
- this.reconnectAttempt = 0;
115
- this.setState('connected');
116
- this.startHeartbeat();
117
- this.flushBuffer();
118
- };
119
- ws.onclose = (ev) => {
120
- if (this.disposed)
121
- return;
122
- // 正常終了 (1000) の場合はリコネクトしない
123
- if (ev.code === 1000) {
124
- console.log(`[ReconnectableWS] 🔒 Closed cleanly (code=${ev.code})`);
125
- return;
126
- }
127
- console.log(`[ReconnectableWS] ❌ Closed (code=${ev.code} reason=${ev.reason})`);
128
- this.stopHeartbeat();
129
- this.scheduleReconnect();
130
- };
131
- ws.onerror = () => {
132
- if (this.disposed)
133
- return;
134
- console.log(`[ReconnectableWS] ⚠️ Error`);
135
- // onclose が後続で発火するため、ここではリコネクトをトリガーしない
136
- };
137
- ws.onmessage = (ev) => {
138
- if (this.disposed)
139
- return;
140
- const data = ev.data;
141
- // Heartbeat pong のフィルタリング
142
- if (data === '__pong') {
143
- this.handlePong();
144
- return;
145
- }
146
- // 通常メッセージをハンドラに配信
147
- for (const handler of this.messageHandlers) {
148
- handler(ev);
149
- }
150
- };
151
- this.ws = ws;
152
- }
153
- setState(state) {
154
- if (this._state === state)
155
- return;
156
- this._state = state;
157
- console.log(`[ReconnectableWS] 📡 State: ${state}`);
158
- this.opts.onConnectionStateChange?.(state);
159
- }
160
- scheduleReconnect() {
161
- if (this.disposed)
162
- return;
163
- if (this.reconnectAttempt >= this.opts.maxReconnectAttempts) {
164
- console.log(`[ReconnectableWS] 🚫 Max reconnect attempts reached (${this.opts.maxReconnectAttempts})`);
165
- this.setState('disconnected');
166
- return;
167
- }
168
- this.setState('reconnecting');
169
- const delay = this.getReconnectDelay();
170
- console.log(`[ReconnectableWS] ⏳ Reconnecting in ${Math.round(delay)}ms (attempt=${this.reconnectAttempt + 1})`);
171
- this.reconnectTimer = setTimeout(() => {
172
- this.reconnectAttempt++;
173
- this.connect();
174
- }, delay);
175
- }
176
- getReconnectDelay() {
177
- const { baseDelay, maxDelay, jitterFactor } = this.opts;
178
- const exponentialDelay = Math.min(baseDelay * Math.pow(2, this.reconnectAttempt), maxDelay);
179
- const jitter = exponentialDelay * jitterFactor * (Math.random() * 2 - 1);
180
- return Math.max(0, exponentialDelay + jitter);
181
- }
182
- flushBuffer() {
183
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
184
- return;
185
- const buffer = this.messageBuffer;
186
- this.messageBuffer = [];
187
- for (const data of buffer) {
188
- try {
189
- this.ws.send(data);
190
- }
191
- catch (e) {
192
- console.warn(`[ReconnectableWS] ⚠️ Failed to flush buffered message:`, e);
193
- }
194
- }
195
- if (buffer.length > 0) {
196
- console.log(`[ReconnectableWS] 📤 Flushed ${buffer.length} buffered messages`);
197
- }
198
- }
199
- // ─── Heartbeat ───────────────────────────────────────────
200
- startHeartbeat() {
201
- this.stopHeartbeat();
202
- this.heartbeatTimer = setInterval(() => {
203
- if (this.ws?.readyState === WebSocket.OPEN) {
204
- this.ws.send('__ping');
205
- this.heartbeatTimeoutTimer = setTimeout(() => {
206
- console.log(`[ReconnectableWS] 💔 Heartbeat timeout`);
207
- // 強制切断 → onclose がリコネクトをトリガー
208
- this.ws?.close(4001, 'heartbeat_timeout');
209
- }, this.opts.heartbeatTimeout);
210
- }
211
- }, this.opts.heartbeatInterval);
212
- }
213
- stopHeartbeat() {
214
- if (this.heartbeatTimer) {
215
- clearInterval(this.heartbeatTimer);
216
- this.heartbeatTimer = null;
217
- }
218
- if (this.heartbeatTimeoutTimer) {
219
- clearTimeout(this.heartbeatTimeoutTimer);
220
- this.heartbeatTimeoutTimer = null;
221
- }
222
- }
223
- handlePong() {
224
- if (this.heartbeatTimeoutTimer) {
225
- clearTimeout(this.heartbeatTimeoutTimer);
226
- this.heartbeatTimeoutTimer = null;
227
- }
228
- }
229
- }
package/dist/room.d.ts DELETED
@@ -1,23 +0,0 @@
1
- type RoomMessageHandler = (data: Record<string, unknown>) => void;
2
- /** WebSocket 互換の send/addEventListener インターフェース */
3
- interface WebSocketLike {
4
- send(data: string): void;
5
- addEventListener(type: 'message', handler: (ev: MessageEvent) => void): void;
6
- }
7
- /** Room の公開インターフェース。WebSocket / BroadcastChannel どちらでも実装可能。 */
8
- export interface RoomLike {
9
- readonly myId: string;
10
- broadcast(msg: Record<string, unknown>): void;
11
- send(id: string, msg: Record<string, unknown>): void;
12
- on(type: string, handler: RoomMessageHandler): void;
13
- }
14
- export declare class Room implements RoomLike {
15
- readonly myId: string;
16
- private ws;
17
- private handlers;
18
- constructor(ws: WebSocketLike, myId: string);
19
- broadcast(msg: Record<string, unknown>): void;
20
- send(id: string, msg: Record<string, unknown>): void;
21
- on(type: string, handler: RoomMessageHandler): void;
22
- }
23
- export {};
package/dist/room.js DELETED
@@ -1,36 +0,0 @@
1
- export class Room {
2
- constructor(ws, myId) {
3
- this.handlers = new Map();
4
- this.ws = ws;
5
- this.myId = myId;
6
- this.ws.addEventListener('message', (ev) => {
7
- let parsed;
8
- try {
9
- parsed = JSON.parse(ev.data);
10
- }
11
- catch {
12
- return;
13
- }
14
- const type = parsed.type;
15
- if (!type)
16
- return;
17
- const from = parsed.__from;
18
- console.log(`[SDK Room] ⬅ recv type=${type} from=${from}`, JSON.stringify(parsed));
19
- const handlers = this.handlers.get(type) || [];
20
- handlers.forEach((h) => h({ ...parsed, __from: from }));
21
- });
22
- }
23
- broadcast(msg) {
24
- console.log(`[SDK Room] ➡ broadcast type=${msg.type}`, JSON.stringify(msg));
25
- this.ws.send(JSON.stringify(msg));
26
- }
27
- send(id, msg) {
28
- console.log(`[SDK Room] ➡ send to=${id} type=${msg.type}`, JSON.stringify(msg));
29
- this.ws.send(JSON.stringify({ ...msg, __to: id }));
30
- }
31
- on(type, handler) {
32
- if (!this.handlers.has(type))
33
- this.handlers.set(type, []);
34
- this.handlers.get(type).push(handler);
35
- }
36
- }
@@ -1,16 +0,0 @@
1
- /**
2
- * @docs
3
- * - ServerAction仕様: docs/docs/uzu_code/connection-method/arch3-authority.md
4
- * - SDK仕様: docs/docs/uzu_code/play-screen-sdk.md
5
- *
6
- * ブラウザ内 LocalGameRoom — GameRoom DO の動作をサーバーレスで再現する。
7
- * server_url がない場合に vite dev だけで ServerAction シナリオをローカル実行する。
8
- *
9
- * online (`runOnlineServerAction`) と挙動を揃えるため、`logic.actions` は同期で実行し
10
- * `sendAction()` 直後に `onState()` を同期発火する (楽観的更新だけで完結する仕様)。
11
- * `logic.serverActions` は `Promise<void>` を返しうるので `await` で実行し、完了後に
12
- * `onState()` を呼ぶ。同名なら actions → serverActions の順で走る (サーバーと同じ)。
13
- */
14
- import type { GameConfig } from '../types.js';
15
- import type { RunHandle } from '../dev-hooks.js';
16
- export declare function runLocalServerAction<S>(config: GameConfig<S>): RunHandle<S>;