bridgewire 1.0.2 → 2.0.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.
Files changed (2) hide show
  1. package/README.md +497 -43
  2. package/package.json +62 -57
package/README.md CHANGED
@@ -1,68 +1,463 @@
1
1
  # BridgeWire
2
2
 
3
- **BridgeWire** is a transport-agnostic client for HTTP and WebSocket communication.
3
+ Transport-agnostic client for HTTP and WebSocket communication.
4
4
 
5
- > ⚠️ BridgeWire is currently in early development! The public API is not stable yet.
5
+ BridgeWire provides a fluent builder API for creating typed transports over `fetch` and `WebSocket`. It is designed to keep request lifecycle handling, subscriptions, timeouts, parsing, and transport selection in one consistent API.
6
6
 
7
- The goal of the project is to provide a unified interface for sending requests over different transports, such as Fetch and WebSocket, with a clean and extensible architecture.
7
+ [Coverage report](https://kuznetsovlv.github.io/bridgewire/)
8
8
 
9
- ## Features
9
+ ## Status
10
10
 
11
- Planned features:
11
+ BridgeWire is under active development.
12
12
 
13
- - Unified API for HTTP Fetch and WebSocket
14
- - Adapter-based architecture
15
- - Request/response abstraction
16
- - Error handling
17
- - Request cancellation
18
- - Extensible transport layer
19
- - Optional reactive layer with RxJS in the future
13
+ Current support:
20
14
 
21
- ## Getting started
15
+ - Fetch-based transport
16
+ - WebSocket-based transport
17
+ - Typed request and response data
18
+ - Request lifecycle statuses
19
+ - Transport-level subscriptions
20
+ - Request-level message, error, abort, and settled events
21
+ - Timeout handling
22
+ - Content-type based default fetch response parser
23
+ - Explicit payload parser selection through `PayloadDataType`
24
+ - GitHub Actions CI
25
+ - HTML coverage report via GitHub Pages
22
26
 
23
- Install dependencies:
27
+ ## Installation
28
+
29
+ After the package is published:
30
+
31
+ ```bash
32
+ npm install bridgewire
33
+ ```
34
+
35
+ For local development:
24
36
 
25
37
  ```bash
38
+ git clone https://github.com/kuznetsovlv/bridgewire.git
39
+ cd bridgewire
26
40
  npm install
27
41
  ```
28
42
 
43
+ ## Basic usage
44
+
45
+ ```ts
46
+ import getBridgeWireBuilder, {
47
+ HTTPMethod,
48
+ PayloadDataType,
49
+ Protocol,
50
+ TransportType,
51
+ } from 'bridgewire';
52
+
53
+ type UserQuery = {
54
+ id: string;
55
+ };
56
+
57
+ type UserResponse = {
58
+ id: string;
59
+ name: string;
60
+ };
61
+
62
+ const transport = getBridgeWireBuilder<UserQuery, UserResponse>()
63
+ .withTransport(TransportType.FETCH)
64
+ .withProtocol(Protocol.HTTPS)
65
+ .withHost('api.example.com')
66
+ .withPath('/users')
67
+ .withMethod(HTTPMethod.GET)
68
+ .withDataType(PayloadDataType.JSON)
69
+ .build();
70
+
71
+ const unsubscribeMessage = transport.onMessage((id, data) => {
72
+ console.log('Message from request:', id, data);
73
+ });
74
+
75
+ const unsubscribeError = transport.onRequestError((id, error) => {
76
+ console.error('Request error:', id, error);
77
+ });
78
+
79
+ const request = transport.send({
80
+ id: '42',
81
+ });
82
+
83
+ console.log(request?.status);
84
+ ```
85
+
86
+ For `GET`, `HEAD`, `DELETE`, and `OPTIONS`, plain object request data is merged into the URL query string.
87
+
88
+ ```ts
89
+ transport.send({
90
+ page: '1',
91
+ search: 'react',
92
+ });
93
+ ```
94
+
95
+ This produces a request URL similar to:
96
+
97
+ ```text
98
+ https://api.example.com/users?page=1&search=react
99
+ ```
100
+
101
+ ## Fetch POST example
102
+
103
+ ```ts
104
+ import getBridgeWireBuilder, {
105
+ HTTPMethod,
106
+ PayloadDataType,
107
+ Protocol,
108
+ TransportType,
109
+ } from 'bridgewire';
110
+
111
+ type CreateUserRequest = {
112
+ name: string;
113
+ };
114
+
115
+ type CreateUserResponse = {
116
+ id: string;
117
+ name: string;
118
+ };
119
+
120
+ const transport = getBridgeWireBuilder<CreateUserRequest, CreateUserResponse>()
121
+ .withTransport(TransportType.FETCH)
122
+ .withProtocol(Protocol.HTTPS)
123
+ .withHost('api.example.com')
124
+ .withPath('/users')
125
+ .withMethod(HTTPMethod.POST)
126
+ .withHeader('Content-Type', 'application/json')
127
+ .withDataType(PayloadDataType.JSON)
128
+ .withTimeout(5000)
129
+ .build();
130
+
131
+ transport.onMessage((id, data) => {
132
+ console.log('Created user:', id, data);
133
+ });
134
+
135
+ transport.onSettled((id, status, error) => {
136
+ console.log('Request settled:', id, status, error);
137
+ });
138
+
139
+ transport.send({
140
+ name: 'Leonid',
141
+ });
142
+ ```
143
+
144
+ For `POST`, `PUT`, and `PATCH`, plain object request data is serialized to JSON.
145
+
146
+ Native Fetch body values are passed through:
147
+
148
+ - `string`
149
+ - `FormData`
150
+ - `Blob`
151
+ - `ArrayBuffer`
152
+ - `ArrayBufferView`
153
+ - `URLSearchParams`
154
+
155
+ ## WebSocket example
156
+
157
+ ```ts
158
+ import getBridgeWireBuilder, {
159
+ PayloadDataType,
160
+ Protocol,
161
+ TransportType,
162
+ } from 'bridgewire';
163
+
164
+ type ClientMessage = {
165
+ type: 'ping';
166
+ };
167
+
168
+ type ServerMessage = {
169
+ type: 'pong';
170
+ };
171
+
172
+ const transport = getBridgeWireBuilder<ClientMessage, ServerMessage>()
173
+ .withTransport(TransportType.WEBSOCKET)
174
+ .withProtocol(Protocol.WSS)
175
+ .withHost('example.com')
176
+ .withPath('/socket')
177
+ .withDataType(PayloadDataType.JSON)
178
+ .withTimeout(5000)
179
+ .build();
180
+
181
+ transport.onMessage((id, data) => {
182
+ console.log('WebSocket message:', id, data);
183
+ });
184
+
185
+ transport.onRequestError((id, error) => {
186
+ console.error('WebSocket request error:', id, error);
187
+ });
188
+
189
+ transport.onError((error) => {
190
+ console.error('Transport error:', error);
191
+ });
192
+
193
+ const request = transport.send({
194
+ type: 'ping',
195
+ });
196
+ ```
197
+
198
+ The current WebSocket model represents a socket stream. While the socket request is active, multiple incoming messages can be emitted through the same request.
199
+
200
+ When `PayloadDataType.JSON` is used, incoming messages are parsed with `JSON.parse`.
201
+
202
+ ## Builder API
203
+
204
+ The main entry point is:
205
+
206
+ ```ts
207
+ getBridgeWireBuilder<RequestData, ResponseData>();
208
+ ```
209
+
210
+ The builder supports fluent configuration:
211
+
212
+ ```ts
213
+ const transport = getBridgeWireBuilder<RequestData, ResponseData>()
214
+ .withTransport(TransportType.FETCH)
215
+ .withProtocol(Protocol.HTTPS)
216
+ .withHost('api.example.com')
217
+ .withPort(443)
218
+ .withPath('/api')
219
+ .withQuery({page: '1'})
220
+ .withHash('top')
221
+ .withMethod(HTTPMethod.GET)
222
+ .withHeader('Accept', 'application/json')
223
+ .withTimeout(5000)
224
+ .build();
225
+ ```
226
+
227
+ ### Transport configuration
228
+
229
+ ```ts
230
+ .withTransport(TransportType.FETCH)
231
+ .withTransport(TransportType.WEBSOCKET)
232
+ ```
233
+
234
+ If transport is omitted, BridgeWire infers it from the configured or default protocol:
235
+
236
+ - `http` / `https` -> Fetch
237
+ - `ws` / `wss` -> WebSocket
238
+
239
+ ### URL configuration
240
+
241
+ ```ts
242
+ .withProtocol(Protocol.HTTPS)
243
+ .withHost('api.example.com')
244
+ .withPort(443)
245
+ .withPath('/users')
246
+ .withQuery({ page: '1', tag: ['react', 'typescript'] })
247
+ .withHash('section')
248
+ .withUrl('https://api.example.com/users?page=1#section')
249
+ ```
250
+
251
+ `withUrl` accepts URL-like strings and applies parsed URL parts to the builder.
252
+
253
+ ### Fetch configuration
254
+
255
+ ```ts
256
+ .withMethod(HTTPMethod.POST)
257
+ .withHeaders({ Accept: 'application/json' })
258
+ .withHeader('Content-Type', 'application/json')
259
+ .withReferrer('about:client')
260
+ .withReferrerPolicy('strict-origin-when-cross-origin')
261
+ .withMode(FetchMode.CORS)
262
+ .withCredentials(FetchCredentials.SAME_ORIGIN)
263
+ .withCache(FetchCache.NO_CACHE)
264
+ .withRedirect(FetchRedirect.FOLLOW)
265
+ .withIntegrity('sha256-...')
266
+ .withKeepAlive()
267
+ ```
268
+
269
+ ### WebSocket configuration
270
+
271
+ ```ts
272
+ .withSoap()
273
+ .withWamp()
274
+ ```
275
+
276
+ These methods enable the corresponding WebSocket subprotocol flags.
277
+
278
+ ### Payload data type
279
+
280
+ ```ts
281
+ .withDataType(PayloadDataType.JSON)
282
+ .withDataType(PayloadDataType.TEXT)
283
+ .withDataType(PayloadDataType.BLOB)
284
+ .withDataType(PayloadDataType.FORM_DATA)
285
+ .withDataType(PayloadDataType.ARRAY_BUFFER)
286
+ ```
287
+
288
+ For Fetch, the data type selects the response parser.
289
+
290
+ For WebSocket, `PayloadDataType.JSON` parses incoming messages with `JSON.parse`, and `PayloadDataType.ARRAY_BUFFER` sets socket `binaryType` to `arraybuffer`.
291
+
292
+ ## Request lifecycle
293
+
294
+ Requests can have the following statuses:
295
+
296
+ ```ts
297
+ RequestStatus.Pending;
298
+ RequestStatus.Completed;
299
+ RequestStatus.Failed;
300
+ RequestStatus.Aborted;
301
+ RequestStatus.TimedOut;
302
+ ```
303
+
304
+ A request is considered settled when it reaches one of these terminal states:
305
+
306
+ - `Completed`
307
+ - `Failed`
308
+ - `Aborted`
309
+ - `TimedOut`
310
+
311
+ ## Transport lifecycle
312
+
313
+ Transports can have the following statuses:
314
+
315
+ ```ts
316
+ TransportStatus.Disconnected;
317
+ TransportStatus.Connecting;
318
+ TransportStatus.Connected;
319
+ TransportStatus.Closing;
320
+ TransportStatus.Error;
321
+ ```
322
+
323
+ ## Subscriptions
324
+
325
+ Transport-level subscriptions:
326
+
327
+ ```ts
328
+ transport.onMessage((id, data) => {
329
+ console.log(id, data);
330
+ });
331
+
332
+ transport.onRequestError((id, error) => {
333
+ console.error(id, error);
334
+ });
335
+
336
+ transport.onAbort((id) => {
337
+ console.log('Aborted:', id);
338
+ });
339
+
340
+ transport.onSettled((id, status, error) => {
341
+ console.log('Settled:', id, status, error);
342
+ });
343
+
344
+ transport.onError((error) => {
345
+ console.error('Transport error:', error);
346
+ });
347
+ ```
348
+
349
+ Each subscription returns an unsubscribe function:
350
+
351
+ ```ts
352
+ const unsubscribe = transport.onMessage((id, data) => {
353
+ console.log(id, data);
354
+ });
355
+
356
+ unsubscribe();
357
+ ```
358
+
359
+ ## Timeouts
360
+
361
+ Timeouts can be configured at builder level:
362
+
363
+ ```ts
364
+ const transport = getBridgeWireBuilder<RequestData, ResponseData>()
365
+ .withTimeout(5000)
366
+ .build();
367
+ ```
368
+
369
+ They can also be overridden per request:
370
+
371
+ ```ts
372
+ transport.send(data, {
373
+ timeout: 1000,
374
+ });
375
+ ```
376
+
377
+ For Fetch, timeout aborts the underlying fetch request.
378
+
379
+ For WebSocket, timeout currently applies to the connection opening phase.
380
+
381
+ ## Compatibility rules
382
+
383
+ BridgeWire validates some incompatible configuration combinations:
384
+
385
+ - `http` / `https` protocols are compatible with Fetch transport
386
+ - `ws` / `wss` protocols are compatible with WebSocket transport
387
+ - `FormData` payload type is not compatible with WebSocket transport
388
+ - `FormData` payload type is not compatible with `ws` / `wss` protocols
389
+
390
+ ## Public API
391
+
392
+ Recommended public imports:
393
+
394
+ ```ts
395
+ import getBridgeWireBuilder, {
396
+ FetchCache,
397
+ FetchCredentials,
398
+ FetchMode,
399
+ FetchRedirect,
400
+ HTTPMethod,
401
+ PayloadDataType,
402
+ Protocol,
403
+ RequestStatus,
404
+ TransportStatus,
405
+ TransportType,
406
+ } from 'bridgewire';
407
+
408
+ import type {
409
+ BridgeWireTransportAbortCallback,
410
+ BridgeWireTransportCallback,
411
+ BridgeWireTransportSettledCallback,
412
+ ErrorCallback,
413
+ Query,
414
+ RequestId,
415
+ RequestOptions,
416
+ UnsubscribeMethod,
417
+ } from 'bridgewire';
418
+ ```
419
+
420
+ Internal implementation details should not be considered stable public API.
421
+
29
422
  ## Development
30
423
 
31
- Run tests in watch mode:
424
+ Install dependencies:
32
425
 
33
426
  ```bash
34
- npm run test:watch
427
+ npm install
35
428
  ```
36
429
 
37
- Run tests once:
430
+ Run tests:
38
431
 
39
432
  ```bash
40
433
  npm run test
41
434
  ```
42
435
 
43
- ## Build
44
-
45
- Build the library:
436
+ Run tests in watch mode:
46
437
 
47
438
  ```bash
48
- npm run build
439
+ npm run test:watch
49
440
  ```
50
441
 
51
- This will:
442
+ Run coverage:
52
443
 
53
- - clean the `dist` folder
54
- - build ESM and CJS bundles using Vite
55
- - generate TypeScript declaration files
444
+ ```bash
445
+ npm run coverage
446
+ ```
447
+
448
+ Open the local HTML coverage report:
56
449
 
57
- ## Lint and format
450
+ ```bash
451
+ xdg-open coverage/index.html
452
+ ```
58
453
 
59
- Run ESLint:
454
+ Run lint:
60
455
 
61
456
  ```bash
62
457
  npm run lint
63
458
  ```
64
459
 
65
- Format code:
460
+ Format files:
66
461
 
67
462
  ```bash
68
463
  npm run format
@@ -74,39 +469,98 @@ Check formatting:
74
469
  npm run format:check
75
470
  ```
76
471
 
77
- ## Full check
472
+ Build package:
473
+
474
+ ```bash
475
+ npm run build
476
+ ```
78
477
 
79
- Run all checks:
478
+ Run full local check:
80
479
 
81
480
  ```bash
82
481
  npm run check
83
482
  ```
84
483
 
85
- Includes:
484
+ ## CI
485
+
486
+ The project uses GitHub Actions for:
487
+
488
+ - formatting check
489
+ - ESLint
490
+ - unit tests
491
+ - coverage generation
492
+ - package build
493
+ - coverage report deployment to GitHub Pages
494
+
495
+ Coverage report:
496
+
497
+ ```text
498
+ https://kuznetsovlv.github.io/bridgewire/
499
+ ```
500
+
501
+ ## Release workflow
502
+
503
+ This project uses Changesets.
504
+
505
+ Create a changeset:
506
+
507
+ ```bash
508
+ npm run changeset
509
+ ```
510
+
511
+ Version packages:
512
+
513
+ ```bash
514
+ npm run version-packages
515
+ ```
516
+
517
+ Release:
518
+
519
+ ```bash
520
+ npm run release
521
+ ```
86
522
 
87
- - lint
88
- - format check
89
- - tests
90
- - build
523
+ Before publishing, review the public exports and make sure only stable API is exported.
91
524
 
92
525
  ## Project structure
93
526
 
94
527
  ```text
95
528
  src/
529
+ BridgeWireBuilder.ts
96
530
  index.ts
97
- index.test.ts
98
- dist/
531
+ types.ts
532
+
533
+ BridgeWireTransport/
534
+ BridgeWireTransport.ts
535
+ FetchBridgeWireTransport.ts
536
+ WebSocketBridgeWireTransport.ts
537
+
538
+ Request/
539
+ Request.ts
540
+ FetchRequest.ts
541
+ WebSocketRequest.ts
542
+
543
+ utils/
544
+ compatibilityAsserts.ts
545
+ isArrayBufferView.ts
546
+ subscribe.ts
547
+ url.ts
99
548
  ```
100
549
 
101
- ## Architecture
550
+ ## Notes
102
551
 
103
- BridgeWire is designed around the idea of transport abstraction:
552
+ BridgeWire is still evolving. The current API is usable, but some details may change before the first stable release.
104
553
 
105
- - Fetch transport
106
- - WebSocket transport
107
- - Unified client interface
554
+ Important areas to review before publishing:
108
555
 
109
- More details will be added as the implementation evolves.
556
+ - public exports
557
+ - README examples
558
+ - generated declaration files
559
+ - package contents
560
+ - compatibility rules
561
+ - WebSocket transport semantics
562
+ - header normalization
563
+ - coverage and CI status
110
564
 
111
565
  ## License
112
566
 
package/package.json CHANGED
@@ -1,60 +1,65 @@
1
1
  {
2
- "name": "bridgewire",
3
- "version": "1.0.2",
4
- "description": "Transport-agnostic client for HTTP and WebSocket communication",
5
- "keywords": [],
6
- "author": "Kuznetsov Leonid",
7
- "license": "MIT",
8
- "type": "module",
9
- "main": "./dist/bridgewire.cjs",
10
- "module": "./dist/bridgewire.js",
11
- "types": "./dist/index.d.ts",
12
- "exports": {
13
- ".": {
14
- "types": "./dist/index.d.ts",
15
- "import": "./dist/bridgewire.js",
16
- "require": "./dist/bridgewire.cjs"
2
+ "name": "bridgewire",
3
+ "version": "2.0.0",
4
+ "description": "Transport-agnostic client for HTTP and WebSocket communication",
5
+ "keywords": [],
6
+ "author": "Kuznetsov Leonid",
7
+ "license": "MIT",
8
+ "type": "module",
9
+ "main": "./dist/bridgewire.cjs",
10
+ "module": "./dist/bridgewire.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/bridgewire.js",
16
+ "require": "./dist/bridgewire.cjs"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/kuznetsovlv/bridgewire.git"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/kuznetsovlv/bridgewire/issues"
28
+ },
29
+ "homepage": "https://github.com/kuznetsovlv/bridgewire#readme",
30
+ "scripts": {
31
+ "dev": "vite",
32
+ "prebuild": "npm run clean",
33
+ "build": "vite build",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest",
36
+ "coverage": "vitest run --coverage",
37
+ "lint": "eslint .",
38
+ "format": "prettier . --write",
39
+ "format:check": "prettier . --check",
40
+ "check": "npm run lint && npm run format:check && npm run test && npm run build",
41
+ "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
42
+ "changeset": "changeset",
43
+ "version-packages": "changeset version",
44
+ "release": "npm run check && changeset publish"
45
+ },
46
+ "devDependencies": {
47
+ "@changesets/cli": "^2.31.0",
48
+ "@eslint/js": "^10.0.1",
49
+ "@trivago/prettier-plugin-sort-imports": "^6.0.2",
50
+ "@types/node": "^25.6.0",
51
+ "@vitest/coverage-v8": "^4.1.7",
52
+ "eslint": "^10.3.0",
53
+ "eslint-config-prettier": "^10.1.8",
54
+ "eslint-plugin-simple-import-sort": "^13.0.0",
55
+ "prettier": "^3.8.3",
56
+ "typescript": "^6.0.3",
57
+ "typescript-eslint": "^8.59.2",
58
+ "vite": "^8.0.10",
59
+ "vite-plugin-dts": "^5.0.0",
60
+ "vitest": "^4.1.5"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
17
64
  }
18
- },
19
- "files": [
20
- "dist"
21
- ],
22
- "repository": {
23
- "type": "git",
24
- "url": "git+https://github.com/kuznetsovlv/bridgewire.git"
25
- },
26
- "bugs": {
27
- "url": "https://github.com/kuznetsovlv/bridgewire/issues"
28
- },
29
- "homepage": "https://github.com/kuznetsovlv/bridgewire#readme",
30
- "scripts": {
31
- "dev": "vite",
32
- "prebuild": "npm run clean",
33
- "build": "vite build",
34
- "test": "vitest run",
35
- "test:watch": "vitest",
36
- "lint": "eslint .",
37
- "format": "prettier . --write",
38
- "format:check": "prettier . --check",
39
- "check": "npm run lint && npm run format:check && npm run test && npm run build",
40
- "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
41
- "changeset": "changeset",
42
- "version-packages": "changeset version",
43
- "release": "npm run check && changeset publish"
44
- },
45
- "devDependencies": {
46
- "@changesets/cli": "^2.31.0",
47
- "@eslint/js": "^10.0.1",
48
- "@types/node": "^25.6.0",
49
- "eslint": "^10.3.0",
50
- "prettier": "^3.8.3",
51
- "typescript": "^6.0.3",
52
- "typescript-eslint": "^8.59.2",
53
- "vite": "^8.0.10",
54
- "vite-plugin-dts": "^5.0.0",
55
- "vitest": "^4.1.5"
56
- },
57
- "publishConfig": {
58
- "access": "public"
59
- }
60
65
  }