cross-tab-worker-databus 0.2.0 → 0.3.0

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/docs/api.md CHANGED
@@ -19,6 +19,17 @@ import {
19
19
  CentrifugeWorkerTransport,
20
20
  createCentrifugeDataBus
21
21
  } from 'cross-tab-worker-databus/centrifuge';
22
+
23
+ import {
24
+ WebSocketTransport,
25
+ createWebSocketDataBus
26
+ } from 'cross-tab-worker-databus';
27
+
28
+ import {
29
+ useCrossTabDataBus,
30
+ useCrossTabStatus,
31
+ useCrossTabSubscription
32
+ } from 'cross-tab-worker-databus/hooks';
22
33
  ```
23
34
 
24
35
  Business integration should prefer `CrossTabDataBus` or `createCentrifugeDataBus`. `WorkerClusterRuntime` is an advanced coordination API.
@@ -68,6 +79,7 @@ Registers a local subscription and returns a cleanup function.
68
79
  - The first handler in the current tab registers a cluster subscription.
69
80
  - The current tab only leaves the topic after the last handler is released.
70
81
  - Subscriptions are automatically queued when the transport is not yet ready.
82
+ - Wildcard subscriptions: a topic ending in `.*` (`chat.*`) matches any remainder, and `*` matches everything. The pattern is routed, owned, and transport-subscribed as a literal channel; publications tagged with a matching concrete topic (or with the pattern itself) are delivered to wildcard handlers. See `topicMatchesPattern` below.
71
83
 
72
84
  ### `unsubscribe(topic, handler?)`
73
85
 
@@ -216,6 +228,66 @@ Available options:
216
228
  - `workerFactory`: custom Dedicated Worker loading method
217
229
  - `sharedWorkerFactory`: custom SharedWorker loading method
218
230
 
231
+ ## WebSocket Transport Backend
232
+
233
+ A dependency-free transport over a plain WebSocket. Any server speaking the JSON frame protocol below can back the same cross-tab clustering stack (owner dedup, sticky routes, failover) as the Centrifuge backend.
234
+
235
+ ### `createWebSocketDataBus<TData>(options)`
236
+
237
+ ```ts
238
+ createWebSocketDataBus<TData>(options): CrossTabDataBus<WebSocketDataBusConfig, TData>
239
+ ```
240
+
241
+ Creates an auto-starting WebSocket DataBus. Defaults: `clusterKey = connection.url`.
242
+
243
+ ```ts
244
+ const bus = createWebSocketDataBus({
245
+ connection: { url: 'wss://example.test/ws' },
246
+ trace: { enabled: true, sink: event => console.log(event) }
247
+ });
248
+ ```
249
+
250
+ ### `WebSocketTransport<TData>`
251
+
252
+ ```ts
253
+ new WebSocketTransport<TData>(connection: WebSocketDataBusConfig)
254
+ ```
255
+
256
+ Implements `DataBusTransport`. Connection lifecycle maps to the DataBus status vocabulary: socket `open` → `connected`, `close` → `disconnected`, `error` → `error` (which triggers DataBus auto-recovery). Subscriptions are re-asserted when a socket reopens in place. Frames dropped while the socket is not open are reported via `handlers.onError`; reopening re-sends subscribe frames.
257
+
258
+ `WebSocketDataBusConfig` fields:
259
+
260
+ - `url` — WebSocket endpoint.
261
+ - `protocols` — optional subprotocol(s) for the handshake.
262
+ - `webSocketFactory` — optional factory `(url, protocols) => WebSocketLike` for tests and non-browser runtimes (defaults to the global `WebSocket`).
263
+
264
+ ### Wire protocol
265
+
266
+ JSON text frames:
267
+
268
+ - client → server: `{"op":"subscribe"|"unsubscribe"|"publish","topic":"...","data":...}`
269
+ - server → client: `{"topic":"...","data":...}` for publications. Frames without a string `topic` field are ignored; malformed JSON surfaces via `handlers.onError` without throwing.
270
+
271
+ A pattern-aware server may deliver publications tagged with the concrete topic (recommended); publications tagged with the pattern itself are delivered through the exact-match path.
272
+
273
+ ## React Hooks (`cross-tab-worker-databus/hooks`)
274
+
275
+ React (>= 18) is an optional peer dependency; this entry is separate so non-React consumers never load it.
276
+
277
+ ### `useCrossTabDataBus(create, deps?)`
278
+
279
+ Creates a bus for the component's lifetime: created on mount, stopped on unmount. StrictMode-safe — the double-invoked effect exercises the same stop/recreate path as BFCache suspend/resume. Returns the active bus or `null` before the first effect (SSR / initial render).
280
+
281
+ Pass a fresh bus per effect run (an inline factory); key recreation through `deps`.
282
+
283
+ ### `useCrossTabSubscription(bus, topic, handler)`
284
+
285
+ Attaches a message handler with automatic cleanup. The handler is read through a ref on each delivery, so inline closures do not cause resubscription across re-renders. Queues while `bus` is `null` or the transport is not ready.
286
+
287
+ ### `useCrossTabStatus(bus)`
288
+
289
+ Mirrors `bus.onStatus()` into React state and reads the current value synchronously whenever the bus identity changes. Returns `'connecting' | 'connected' | 'disconnected' | 'error'`.
290
+
219
291
  ## `WorkerClusterRuntime`
220
292
 
221
293
  Advanced API responsible for Worker registration, heartbeat, visibility, routing, BroadcastChannel protocol, and migration. Business modules should not operate on it directly.
@@ -257,5 +329,7 @@ Selects the actual backend based on `WorkerMode` and capability detection, retur
257
329
  - `selectLeastLoadedWorker`
258
330
  - `selectRebalanceTarget`
259
331
  - `hasActiveOwner`
332
+ - `isWildcardTopic(pattern)`
333
+ - `topicMatchesPattern(pattern, topic)` — wildcard matching used by subscriptions: `chat.*` matches `chat.room.1` (segment-boundary prefix), `*` matches everything
260
334
 
261
335
  These pure functions are primarily used for testing, diagnostics, and custom coordination strategies.
@@ -12,12 +12,19 @@ The package provides the following entry points:
12
12
 
13
13
  - `cross-tab-worker-databus`: the core DataBus and transport interfaces
14
14
  - `cross-tab-worker-databus/centrifuge`: the built-in Centrifuge Worker transport
15
+ - `cross-tab-worker-databus/centrifuge.worker`: the Dedicated Worker build artifact, loaded by default by the built-in factory; typically no need to reference it directly
15
16
  - `cross-tab-worker-databus/centrifuge.shared.worker`: the SharedWorker build artifact, loaded by default by the built-in factory; typically no need to reference it directly
17
+ - `cross-tab-worker-databus/hooks`: optional React hooks adapter (`useCrossTabDataBus`, `useCrossTabSubscription`, `useCrossTabStatus`); React (>= 18) is an optional peer dependency
18
+ - `cross-tab-worker-databus` also exports a zero-dependency native `WebSocketTransport` / `createWebSocketDataBus` for servers that speak plain WebSockets
19
+
20
+ The `cross-tab-worker-databus/centrifuge` entry point relies on the optional peer dependency `centrifuge` (^5.5.3). Install it alongside this package when using the built-in Centrifuge transport: `pnpm add centrifuge`.
16
21
 
17
22
  ## 2. Creating an Instance
18
23
 
19
24
  It is recommended to create an instance in the application's infrastructure layer and have other modules import it directly. This way, business modules within the same Tab share the Worker, connection, and Topic references.
20
25
 
26
+ Both module formats are published: ESM (`import`) and CommonJS (`require` / `dist/cjs`), so CJS bundler configurations and `require()` callers work out of the box.
27
+
21
28
  ```ts
22
29
  import { createCentrifugeDataBus } from 'cross-tab-worker-databus/centrifuge';
23
30
 
@@ -59,8 +59,9 @@ transport only owns the I/O path: connect, subscribe, publish, disconnect.
59
59
 
60
60
  Mirror the Centrifuge backend's `centrifuge-protocol.ts`: a discriminated union
61
61
  of messages the main thread sends to the Worker (`INIT` / `SUBSCRIBE` /
62
- `UNSUBSCRIBE` / `PUBLISH` / `STOP`) and a union the Worker posts back
63
- (`STATUS` / `MESSAGE` / `ERROR`). Keep it structured-cloneable (no functions,
62
+ `UNSUBSCRIBE` / `PUBLISH` / `PUBLISH_BIN` / `PING` / `STOP`) and a union the
63
+ Worker posts back (`STATUS` / `MESSAGE` / `MESSAGE_BIN` / `ERROR`). Keep it
64
+ structured-cloneable (no functions,
64
65
  no class instances — `Error` must be serialised).
65
66
 
66
67
  ### 2. Implement the session
@@ -124,8 +125,35 @@ opt in:
124
125
  }
125
126
  ```
126
127
 
128
+ ## Built-in: native WebSocket backend
129
+
130
+ The package ships a second real backend, `WebSocketTransport`, proving the
131
+ contract above with zero dependencies. Use it when your server already speaks
132
+ WebSockets and you do not need Centrifugo features.
133
+
134
+ ```ts
135
+ import { createWebSocketDataBus } from 'cross-tab-worker-databus';
136
+
137
+ const bus = createWebSocketDataBus({
138
+ connection: { url: 'wss://example.test/ws' }
139
+ });
140
+ ```
141
+
142
+ Wire protocol (JSON text frames):
143
+
144
+ - client → server: `{"op":"subscribe"|"unsubscribe"|"publish","topic":"...","data":...}`
145
+ - server → client: publications are `{"topic":"...","data":...}`. Frames
146
+ without a string `topic` are ignored; malformed JSON is reported through
147
+ `handlers.onError` without throwing.
148
+
149
+ Lifecycle mapping: `open` → `connected`, `close` → `disconnected`,
150
+ `error` → `error` (DataBus auto-recovery). Subscribe frames are re-sent when
151
+ the socket reopens in place. A pattern-aware server may tag publications with
152
+ the concrete topic — see wildcard subscriptions in [api.md](./api.md).
153
+
127
154
  ## Factory entry point
128
155
 
156
+
129
157
  Provide a `create<Backend>DataBus(options)` factory that wires the transport
130
158
  into a `CrossTabDataBus`, mirroring `createCentrifugeDataBus`. This is the
131
159
  surface most consumers use; it should accept the connection config, cluster
package/docs/zh/README.md CHANGED
@@ -8,6 +8,7 @@
8
8
  |---|---|
9
9
  | [快速接入](./getting-started.md) | 安装、创建实例、订阅、发布和销毁 |
10
10
  | [配置说明](./configuration.md) | 核心配置、Centrifuge 配置、默认值和约束 |
11
+ | [Transport 后端](./transports.md) | `DataBusTransport` 契约、Worker 协议与第三方后端接入指南 |
11
12
  | [API 参考](./api.md) | 公共入口、类型、方法、返回值和行为 |
12
13
  | [架构说明](./architecture.md) | Worker 集群、路由、存储、迁移和降级设计 |
13
14
  | [能力矩阵](./capabilities.md) | 已实现、未实现和计划待实现的能力矩阵 |
package/docs/zh/api.md CHANGED
@@ -19,6 +19,17 @@ import {
19
19
  CentrifugeWorkerTransport,
20
20
  createCentrifugeDataBus
21
21
  } from 'cross-tab-worker-databus/centrifuge';
22
+
23
+ import {
24
+ WebSocketTransport,
25
+ createWebSocketDataBus
26
+ } from 'cross-tab-worker-databus';
27
+
28
+ import {
29
+ useCrossTabDataBus,
30
+ useCrossTabStatus,
31
+ useCrossTabSubscription
32
+ } from 'cross-tab-worker-databus/hooks';
22
33
  ```
23
34
 
24
35
  业务接入优先使用 `CrossTabDataBus` 或 `createCentrifugeDataBus`。`WorkerClusterRuntime` 属于高级协调 API。
@@ -68,6 +79,7 @@ subscribe(
68
79
  - 当前 Tab 第一个 handler 会登记集群订阅。
69
80
  - 最后一个 handler 释放后,当前 Tab 才退出该 Topic。
70
81
  - transport 尚未 ready 时订阅自动排队。
82
+ - 通配符订阅:以 `.*` 结尾的 Topic(如 `chat.*`)匹配任意后缀,`*` 匹配全部。pattern 以字面量参与路由、归属与传输订阅;携带匹配的具体 topic(或 pattern 本身)的发布都会投递给通配 handler。匹配规则见下方 `topicMatchesPattern`。
71
83
 
72
84
  ### `unsubscribe(topic, handler?)`
73
85
 
@@ -216,6 +228,66 @@ const transport = new CentrifugeWorkerTransport({
216
228
  - `workerFactory`:自定义 Dedicated Worker 加载方式
217
229
  - `sharedWorkerFactory`:自定义 SharedWorker 加载方式
218
230
 
231
+ ## WebSocket 传输后端
232
+
233
+ 基于原生 WebSocket 的零依赖传输。任何实现下列 JSON 帧协议的服务器都能驱动与 Centrifuge 后端相同的跨 Tab 集群栈(owner 去重、粘性路由、故障转移)。
234
+
235
+ ### `createWebSocketDataBus<TData>(options)`
236
+
237
+ ```ts
238
+ createWebSocketDataBus<TData>(options): CrossTabDataBus<WebSocketDataBusConfig, TData>
239
+ ```
240
+
241
+ 创建自动启动的 WebSocket DataBus。默认值:`clusterKey = connection.url`。
242
+
243
+ ```ts
244
+ const bus = createWebSocketDataBus({
245
+ connection: { url: 'wss://example.test/ws' },
246
+ trace: { enabled: true, sink: event => console.log(event) }
247
+ });
248
+ ```
249
+
250
+ ### `WebSocketTransport<TData>`
251
+
252
+ ```ts
253
+ new WebSocketTransport<TData>(connection: WebSocketDataBusConfig)
254
+ ```
255
+
256
+ 实现 `DataBusTransport`。连接生命周期直接映射 DataBus 状态:socket `open` → `connected`,`close` → `disconnected`,`error` → `error`(触发 DataBus 自动恢复)。socket 原地重连时会自动重发订阅;socket 未打开期间被丢弃的帧通过 `handlers.onError` 上报,重开后自动补发订阅帧。
257
+
258
+ `WebSocketDataBusConfig` 字段:
259
+
260
+ - `url` — WebSocket 端点。
261
+ - `protocols` — 可选的握手子协议。
262
+ - `webSocketFactory` — 可选工厂 `(url, protocols) => WebSocketLike`,用于测试与非浏览器运行时(默认使用全局 `WebSocket`)。
263
+
264
+ ### 线协议
265
+
266
+ JSON 文本帧:
267
+
268
+ - client → server:`{"op":"subscribe"|"unsubscribe"|"publish","topic":"...","data":...}`
269
+ - server → client:发布为 `{"topic":"...","data":...}`。没有字符串 `topic` 字段的帧被忽略;非法 JSON 通过 `handlers.onError` 上报而不会抛出。
270
+
271
+ 支持 pattern 的服务器建议以具体 topic 标注发布;以 pattern 本身标注的发布走精确匹配路径投递。
272
+
273
+ ## React Hooks(`cross-tab-worker-databus/hooks`)
274
+
275
+ React(>= 18)是可选 peer 依赖;独立入口保证非 React 消费者不会加载它。
276
+
277
+ ### `useCrossTabDataBus(create, deps?)`
278
+
279
+ 创建随组件生命周期存活的 bus:挂载时创建,卸载时停止。StrictMode 安全——effect 双调用走的是与 BFCache 挂起/恢复相同的停止/重建路径。返回当前 bus;首次 effect 之前(SSR / 初始渲染)为 `null`。
280
+
281
+ 每次 effect 返回一个全新 bus(内联工厂即可);需要重建时通过 `deps` 控制。
282
+
283
+ ### `useCrossTabSubscription(bus, topic, handler)`
284
+
285
+ 登记消息 handler 并自动清理。handler 经由 ref 在每次投递时读取,因此内联闭包不会导致重渲染时的重订阅。`bus` 为 `null` 或 transport 未 ready 时自动排队。
286
+
287
+ ### `useCrossTabStatus(bus)`
288
+
289
+ 把 `bus.onStatus()` 镜像为 React 状态,bus 身份变化时同步读取当前值。返回 `'connecting' | 'connected' | 'disconnected' | 'error'`。
290
+
219
291
  ## `WorkerClusterRuntime`
220
292
 
221
293
  高级 API,负责 Worker 注册、心跳、可见性、路由、BroadcastChannel 协议和迁移。业务模块不应直接操作它。
@@ -257,5 +329,7 @@ const transport = new CentrifugeWorkerTransport({
257
329
  - `selectLeastLoadedWorker`
258
330
  - `selectRebalanceTarget`
259
331
  - `hasActiveOwner`
332
+ - `isWildcardTopic(pattern)`
333
+ - `topicMatchesPattern(pattern, topic)` — 订阅使用的通配匹配:`chat.*` 匹配 `chat.room.1`(按段前缀),`*` 匹配全部
260
334
 
261
335
  这些纯函数主要用于测试、诊断和自定义协调策略。
@@ -12,12 +12,19 @@ pnpm add cross-tab-worker-databus
12
12
 
13
13
  - `cross-tab-worker-databus`:核心 DataBus 和 transport 接口
14
14
  - `cross-tab-worker-databus/centrifuge`:内置 Centrifuge Worker transport
15
+ - `cross-tab-worker-databus/centrifuge.worker`:Dedicated Worker 构建产物,默认由内置 factory 加载,通常无需直接引用
15
16
  - `cross-tab-worker-databus/centrifuge.shared.worker`:SharedWorker 构建产物,默认由内置 factory 加载,通常无需直接引用
17
+ - `cross-tab-worker-databus/hooks`:可选的 React hooks 适配层(`useCrossTabDataBus`、`useCrossTabSubscription`、`useCrossTabStatus`);React(>= 18)为可选 peer 依赖
18
+ - `cross-tab-worker-databus` 同时导出零依赖的原生 `WebSocketTransport` / `createWebSocketDataBus`,适用于本身使用 WebSocket 的服务器
19
+
20
+ `cross-tab-worker-databus/centrifuge` 入口依赖可选 peer dependency `centrifuge`(^5.5.3)。使用内置 Centrifuge transport 时请一并安装:`pnpm add centrifuge`。
16
21
 
17
22
  ## 2. 创建实例
18
23
 
19
24
  建议在应用基础设施层创建一个实例,其他模块直接导入。这样同一 Tab 内的业务模块会共享 Worker、连接和 Topic 引用。
20
25
 
26
+ 包同时发布 ESM 与 CommonJS 双格式:`import` 与 `require()`(`dist/cjs`)均可直接使用,CJS bundler 配置无需额外处理。
27
+
21
28
  ```ts
22
29
  import { createCentrifugeDataBus } from 'cross-tab-worker-databus/centrifuge';
23
30
 
@@ -54,8 +54,9 @@ DataBus 层负责跨 Tab 协调(BroadcastChannel 控制面、localStorage 路
54
54
  ### 1. 定义你的 Worker 协议
55
55
 
56
56
  参照 Centrifuge 后端的 `centrifuge-protocol.ts`:一个主线程发给 Worker 的
57
- 判别联合(`INIT` / `SUBSCRIBE` / `UNSUBSCRIBE` / `PUBLISH` / `STOP`)和一个
58
- Worker 回传的联合(`STATUS` / `MESSAGE` / `ERROR`)。保持结构化克隆安全
57
+ 判别联合(`INIT` / `SUBSCRIBE` / `UNSUBSCRIBE` / `PUBLISH` / `PUBLISH_BIN` /
58
+ `PING` / `STOP`)和一个 Worker 回传的联合(`STATUS` / `MESSAGE` / `MESSAGE_BIN` /
59
+ `ERROR`)。保持结构化克隆安全
59
60
  (无函数、无类实例——`Error` 必须序列化)。
60
61
 
61
62
  ### 2. 实现 session
@@ -113,8 +114,32 @@ shared worker):
113
114
  }
114
115
  ```
115
116
 
117
+ ## 内置:原生 WebSocket 后端
118
+
119
+ 包内自带第二个真实后端 `WebSocketTransport`,以零依赖验证了上述契约。当你的
120
+ 服务器本身使用 WebSocket、且不需要 Centrifugo 特性时可以直接使用。
121
+
122
+ ```ts
123
+ import { createWebSocketDataBus } from 'cross-tab-worker-databus';
124
+
125
+ const bus = createWebSocketDataBus({
126
+ connection: { url: 'wss://example.test/ws' }
127
+ });
128
+ ```
129
+
130
+ 线协议(JSON 文本帧):
131
+
132
+ - client → server:`{"op":"subscribe"|"unsubscribe"|"publish","topic":"...","data":...}`
133
+ - server → client:发布为 `{"topic":"...","data":...}`。没有字符串 `topic` 的帧
134
+ 会被忽略;非法 JSON 通过 `handlers.onError` 上报而不会抛出。
135
+
136
+ 生命周期映射:`open` → `connected`,`close` → `disconnected`,`error` → `error`
137
+ (触发 DataBus 自动恢复)。socket 原地重连时自动重发订阅帧。支持 pattern 的
138
+ 服务器可以以具体 topic 标注发布——见 [api.md](../api.md) 中的通配符订阅。
139
+
116
140
  ## 工厂入口
117
141
 
142
+
118
143
  提供一个 `create<Backend>DataBus(options)` 工厂,把 transport 接入
119
144
  `CrossTabDataBus`,与 `createCentrifugeDataBus` 对称。这是大多数消费者使用的
120
145
  界面;它应接受连接配置、cluster key(默认为连接 URL),并把 trace /
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cross-tab-worker-databus",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Framework-agnostic cross-tab data bus with Dedicated/Shared Worker clustering and Centrifuge support.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,48 +29,82 @@
29
29
  "README.md",
30
30
  "LICENSE"
31
31
  ],
32
- "main": "./dist/index.js",
32
+ "main": "./dist/cjs/index.cjs",
33
33
  "module": "./dist/index.js",
34
34
  "types": "./dist/index.d.ts",
35
35
  "exports": {
36
36
  ".": {
37
37
  "types": "./dist/index.d.ts",
38
- "import": "./dist/index.js"
38
+ "import": "./dist/index.js",
39
+ "require": "./dist/cjs/index.cjs"
39
40
  },
40
41
  "./centrifuge": {
41
42
  "types": "./dist/centrifuge.d.ts",
42
- "import": "./dist/centrifuge.js"
43
+ "import": "./dist/centrifuge.js",
44
+ "require": "./dist/cjs/centrifuge.cjs"
45
+ },
46
+ "./centrifuge.worker": {
47
+ "types": "./dist/workers/centrifuge.worker.d.ts",
48
+ "default": "./dist/centrifuge.worker.js"
49
+ },
50
+ "./centrifuge.shared.worker": {
51
+ "types": "./dist/workers/centrifuge.shared.worker.d.ts",
52
+ "default": "./dist/centrifuge.shared.worker.js"
53
+ },
54
+ "./hooks": {
55
+ "types": "./dist/hooks.d.ts",
56
+ "import": "./dist/hooks.js",
57
+ "require": "./dist/cjs/hooks.cjs"
43
58
  },
44
- "./centrifuge.worker": "./dist/centrifuge.worker.js",
45
- "./centrifuge.shared.worker": "./dist/centrifuge.shared.worker.js",
46
59
  "./package.json": "./package.json"
47
60
  },
61
+ "sideEffects": [
62
+ "./dist/centrifuge.worker.js",
63
+ "./dist/centrifuge.shared.worker.js"
64
+ ],
48
65
  "scripts": {
49
66
  "build": "node scripts/build.mjs",
50
- "check": "pnpm typecheck && pnpm test && pnpm build",
67
+ "check": "pnpm typecheck && pnpm build && pnpm test",
51
68
  "examples": "node scripts/serve-examples.mjs",
69
+ "lint": "eslint .",
52
70
  "test": "vitest run",
53
71
  "test:watch": "vitest",
72
+ "test:coverage": "vitest run --coverage",
54
73
  "test:e2e": "pnpm build && playwright test",
55
- "typecheck": "tsc --noEmit"
74
+ "typecheck": "tsc --noEmit",
75
+ "prepublishOnly": "pnpm check"
56
76
  },
57
77
  "peerDependencies": {
58
- "centrifuge": "^5.5.3"
78
+ "centrifuge": "^5.5.3",
79
+ "react": ">=18"
59
80
  },
60
81
  "peerDependenciesMeta": {
61
82
  "centrifuge": {
62
83
  "optional": true
84
+ },
85
+ "react": {
86
+ "optional": true
63
87
  }
64
88
  },
65
89
  "devDependencies": {
90
+ "@eslint/js": "^9.39.5",
66
91
  "@playwright/test": "^1.62.1",
92
+ "@testing-library/react": "^16.3.3",
67
93
  "@types/node": "^24.0.0",
94
+ "@types/react": "^18.3.31",
95
+ "@vitest/coverage-v8": "^3.2.7",
68
96
  "esbuild": "^0.25.0",
97
+ "eslint": "^9.39.4",
98
+ "globals": "^17.11.0",
99
+ "jsdom": "^25.0.1",
100
+ "react": "^18.3.1",
101
+ "react-dom": "^18.3.1",
69
102
  "typescript": "^5.9.0",
103
+ "typescript-eslint": "^8.68.0",
70
104
  "vitest": "^3.2.0"
71
105
  },
72
106
  "engines": {
73
107
  "node": ">=18.0.0"
74
108
  },
75
109
  "packageManager": "pnpm@10.14.0"
76
- }
110
+ }