@reddb-io/client 1.15.0 → 1.16.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/README.md CHANGED
@@ -147,6 +147,14 @@ The client follows the SDK Helper Spec for the shared JS/TS surface:
147
147
  | `grpcs://` | gRPC over TLS | 55555 |
148
148
  | `http://` | HTTP JSON | 5000 |
149
149
  | `https://` | HTTPS JSON | 55555 |
150
+ | `ws://` | RedWire over WebSocket | 80 |
151
+ | `wss://` | RedWire over WebSocket + TLS | 443 |
152
+ | `red+ws://` | RedWire over WebSocket | 80 |
153
+ | `red+wss://` | RedWire over WebSocket + TLS | 443 |
154
+
155
+ Browser bundles cannot open raw TCP, so use `wss://host` (or `ws://host`
156
+ for plaintext development origins) when connecting directly from browser
157
+ JavaScript.
150
158
 
151
159
  ## Rejected URI schemes
152
160
 
@@ -13,8 +13,9 @@
13
13
  * - It omits `splitNdjson()` (a `node:stream` `Transform`), which has no
14
14
  * browser counterpart.
15
15
  *
16
- * `connect()` reaches `http(s)://` and `red+wss://` (RedWire-over-binary-
17
- * WebSocket, #937) from a browser; `grpc(s)://`, `red(s)://`, and `pg`
16
+ * `connect()` reaches `http(s)://`, `ws(s)://`, and `red+ws(s)://`
17
+ * (RedWire-over-binary-WebSocket, #937) from a browser; `grpc(s)://`,
18
+ * `red(s)://`, and `pg`
18
19
  * throw `RedDBError` with code `'BROWSER_TRANSPORT_UNSUPPORTED'`, and
19
20
  * embedded URIs throw `EmbeddedNotSupported`.
20
21
  */
@@ -422,8 +423,8 @@ export class RedDB {
422
423
  /**
423
424
  * Connect to a remote RedDB instance from a browser.
424
425
  *
425
- * `http://host:port`, `https://host:port`, and `red+wss://host:port`
426
- * (RedWire-over-binary-WebSocket, #937) are reachable from a browser
426
+ * `http://host:port`, `https://host:port`, `ws(s)://host:port`, and
427
+ * `red+ws(s)://host:port` (RedWire-over-binary-WebSocket, #937) are reachable from a browser
427
428
  * sandbox. `grpc(s)://`, `red(s)://`, and `pg` throw `RedDBError` with code
428
429
  * `'BROWSER_TRANSPORT_UNSUPPORTED'`. Embedded URIs (`memory://`, `memory:`,
429
430
  * `file:///path`, `red:///`, `red://:memory[:]`) throw `EmbeddedNotSupported`.
@@ -437,9 +438,10 @@ export function login(
437
438
  ): Promise<LoginResult>
438
439
 
439
440
  export interface ParsedUri {
440
- kind: 'embedded' | 'http' | 'https' | 'red' | 'reds' | 'redwss' | 'grpc' | 'grpcs' | 'pg'
441
+ kind: 'embedded' | 'http' | 'https' | 'red' | 'reds' | 'redws' | 'redwss' | 'grpc' | 'grpcs' | 'pg'
441
442
  host?: string
442
443
  port?: number
444
+ tls?: boolean
443
445
  path?: string
444
446
  username?: string
445
447
  password?: string
package/index.d.ts CHANGED
@@ -443,9 +443,10 @@ export function login(
443
443
  ): Promise<LoginResult>
444
444
 
445
445
  export interface ParsedUri {
446
- kind: 'embedded' | 'http' | 'https' | 'red' | 'reds' | 'redwss' | 'grpc' | 'grpcs' | 'pg'
446
+ kind: 'embedded' | 'http' | 'https' | 'red' | 'reds' | 'redws' | 'redwss' | 'grpc' | 'grpcs' | 'pg'
447
447
  host?: string
448
448
  port?: number
449
+ tls?: boolean
449
450
  path?: string
450
451
  username?: string
451
452
  password?: string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reddb-io/client",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "Thin remote-only RedDB driver. Downloads the `red_client` binary on install. Speaks RedWire/gRPC/HTTP. Embedded URIs (memory://, file://, red:///path) are rejected — use @reddb-io/sdk for those.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/core/url.js CHANGED
@@ -20,7 +20,7 @@ import { RedDBError } from './errors.js'
20
20
 
21
21
  /**
22
22
  * @typedef {object} ParsedUri
23
- * @property {'embedded' | 'http' | 'https' | 'red' | 'reds' | 'redwss' | 'grpc' | 'grpcs' | 'pg'} kind
23
+ * @property {'embedded' | 'http' | 'https' | 'red' | 'reds' | 'redws' | 'redwss' | 'grpc' | 'grpcs' | 'pg'} kind
24
24
  * @property {string} [host]
25
25
  * @property {number} [port]
26
26
  * @property {string} [path] // for embedded `file://`-equivalent
@@ -35,8 +35,8 @@ import { RedDBError } from './errors.js'
35
35
 
36
36
  /**
37
37
  * Parse any URI string into a normalised `ParsedUri`.
38
- * Accepts `red://`, `memory://`, `file://`, `grpc://` (the latter
39
- * three for backwards compat).
38
+ * Accepts `red://`, `ws(s)://`, `memory://`, `file://`, `grpc://`
39
+ * (the latter three for backwards compat).
40
40
  *
41
41
  * @param {string} uri
42
42
  * @returns {ParsedUri}
@@ -48,7 +48,16 @@ export function parseUri(uri) {
48
48
  )
49
49
  }
50
50
  if (uri.startsWith('red+wss://')) {
51
- return parseRedWssUrl(uri)
51
+ return parseRedWebSocketUrl(uri, 'red+wss')
52
+ }
53
+ if (uri.startsWith('red+ws://')) {
54
+ return parseRedWebSocketUrl(uri, 'red+ws')
55
+ }
56
+ if (uri.startsWith('wss://')) {
57
+ return parseRedWebSocketUrl(uri, 'wss')
58
+ }
59
+ if (uri.startsWith('ws://')) {
60
+ return parseRedWebSocketUrl(uri, 'ws')
52
61
  }
53
62
  if (uri.startsWith('red://') || uri === 'red:' || uri === 'red:/') {
54
63
  return parseRedUrl(uri)
@@ -57,28 +66,35 @@ export function parseUri(uri) {
57
66
  }
58
67
 
59
68
  /**
60
- * Parse `red+wss://[user:pass@]host:port[?token=...]` — RedWire framing
69
+ * Parse `ws(s)://[user:pass@]host:port[?token=...]` — RedWire framing
61
70
  * tunneled over a binary WebSocket (the browser transport; #937, ADR
62
- * 0036). Resolves to a `wss://host:port/redwire` endpoint downstream.
63
- * Default port is 443 (the TLS edge).
71
+ * 0036). Resolves to a `ws(s)://host:port/redwire` endpoint downstream.
72
+ * TLS defaults to port 443; plaintext defaults to port 80.
64
73
  */
65
74
  export function parseRedWssUrl(uri) {
75
+ return parseRedWebSocketUrl(uri, 'red+wss')
76
+ }
77
+
78
+ function parseRedWebSocketUrl(uri, scheme) {
79
+ const tls = scheme === 'red+wss' || scheme === 'wss'
80
+ const prefix = `${scheme}://`
66
81
  let parsed
67
82
  try {
68
83
  // Borrow the URL parser via an `https://` authority so user / pass /
69
84
  // host / port / query all decode with the standard rules.
70
- parsed = new URL('https://' + uri.slice('red+wss://'.length))
85
+ parsed = new URL('https://' + uri.slice(prefix.length))
71
86
  } catch (err) {
72
87
  throw new RedDBError('UNPARSEABLE_URI', `failed to parse '${uri}': ${err.message}`)
73
88
  }
74
89
  if (!parsed.hostname) {
75
- throw new TypeError(`invalid red+wss:// URI: missing host in '${uri}'`)
90
+ throw new TypeError(`invalid ${prefix} URI: missing host in '${uri}'`)
76
91
  }
77
92
  const params = parsed.searchParams
78
93
  return {
79
- kind: 'redwss',
94
+ kind: tls ? 'redwss' : 'redws',
80
95
  host: parsed.hostname,
81
- port: parsed.port ? Number(parsed.port) : 443,
96
+ port: parsed.port ? Number(parsed.port) : (tls ? 443 : 80),
97
+ tls,
82
98
  username: parsed.username ? decodeURIComponent(parsed.username) : undefined,
83
99
  password: parsed.password ? decodeURIComponent(parsed.password) : undefined,
84
100
  token: params.get('token') ?? undefined,
@@ -239,7 +255,7 @@ export function parseLegacyUrl(uri) {
239
255
  }
240
256
  throw new RedDBError(
241
257
  'UNSUPPORTED_SCHEME',
242
- `unsupported URI: '${uri}'. Use 'red://...' or one of memory://, file://, grpc://, http(s)://`,
258
+ `unsupported URI: '${uri}'. Use 'red://...' or one of memory://, file://, grpc://, http(s)://, ws(s)://`,
243
259
  )
244
260
  }
245
261
 
@@ -280,6 +296,10 @@ function defaultPortFor(kind) {
280
296
  case 'reds':
281
297
  case 'redwire':
282
298
  return 5050
299
+ case 'redws':
300
+ return 80
301
+ case 'redwss':
302
+ return 443
283
303
  case 'grpc':
284
304
  return 55055
285
305
  case 'grpcs':
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * - 'http://host:port' — HTTP JSON-RPC over `fetch`
9
9
  * - 'https://host:port' — HTTPS JSON-RPC over `fetch`
10
+ * - 'ws(s)://host:port' — RedWire over binary WebSocket
10
11
  *
11
12
  * Streaming is the Web-streams implementation (`./streaming-web.js`), so the
12
13
  * full transport-agnostic surface works client-side: query, execute, insert,
@@ -72,18 +73,18 @@ function browserTransportError(scheme) {
72
73
  `'${scheme}' connections are not available in the browser: the `
73
74
  + `browser sandbox exposes no raw TCP socket (red://, reds://, pg) or `
74
75
  + `HTTP/2 client (grpc://, grpcs://) to JavaScript. Connect to an `
75
- + `HTTP(S) endpoint instead — e.g. 'http://host:port' / `
76
- + `'https://host:port' by running RedDB's HTTP JSON-RPC listener or `
77
- + `an HTTP gateway in front of the server, then call `
78
- + `connect('https://…').`,
76
+ + `HTTP(S) endpoint or RedWire WebSocket endpoint instead — e.g. `
77
+ + `'http://host:port' / 'https://host:port' / 'wss://host' by running `
78
+ + `RedDB's HTTP JSON-RPC listener, its WebSocket edge, or an HTTP gateway `
79
+ + `in front of the server.`,
79
80
  )
80
81
  }
81
82
 
82
83
  /**
83
84
  * Connect to a remote RedDB instance from a browser.
84
85
  *
85
- * @param {string} uri Connection URI. Only `http(s)://` is reachable from a
86
- * browser; other schemes raise `BROWSER_TRANSPORT_UNSUPPORTED`.
86
+ * @param {string} uri Connection URI. `http(s)://` and `ws(s)://` are
87
+ * reachable from a browser; other schemes raise `BROWSER_TRANSPORT_UNSUPPORTED`.
87
88
  * @param {object} [options]
88
89
  * @param {object} [options.auth] Authentication credentials.
89
90
  * @param {string} [options.auth.token] Bearer / API-key token.
@@ -128,11 +129,12 @@ export async function connect(uri, options = {}) {
128
129
  // a WSS the sandbox can open. The TLS edge enforces the Origin allowlist
129
130
  // and WSS-only on its side; here we just open the socket and run the
130
131
  // standard RedWire handshake over it.
131
- if (parsed.kind === 'redwss') {
132
+ if (parsed.kind === 'redws' || parsed.kind === 'redwss') {
132
133
  const merged = mergeAuthFromUri(parsed, options.auth)
133
134
  let token = merged.token
134
135
  if (!token && merged.username && merged.password) {
135
- const loginUrl = merged.loginUrl ?? `https://${parsed.host}:${parsed.port}/auth/login`
136
+ const httpScheme = parsed.tls === false ? 'http' : 'https'
137
+ const loginUrl = merged.loginUrl ?? `${httpScheme}://${parsed.host}:${parsed.port}/auth/login`
136
138
  const session = await login(loginUrl, {
137
139
  username: merged.username,
138
140
  password: merged.password,
@@ -140,7 +142,8 @@ export async function connect(uri, options = {}) {
140
142
  token = session.token
141
143
  }
142
144
  const auth = token ? { kind: 'bearer', token } : { kind: 'anonymous' }
143
- const url = `wss://${parsed.host}:${parsed.port}${REDWIRE_WS_PATH}`
145
+ const wsScheme = parsed.tls === false ? 'ws' : 'wss'
146
+ const url = `${wsScheme}://${parsed.host}:${parsed.port}${REDWIRE_WS_PATH}`
144
147
  const client = await connectRedwireWs({ url, auth })
145
148
  return new RedDB(client)
146
149
  }
package/src/redwire-ws.js CHANGED
@@ -128,7 +128,7 @@ function waitOpen(ws) {
128
128
  * Open a RedWire connection over a binary WebSocket.
129
129
  *
130
130
  * @param {object} opts
131
- * @param {string} [opts.url] `wss://host:port/redwire` endpoint.
131
+ * @param {string} [opts.url] `ws(s)://host:port/redwire` endpoint.
132
132
  * @param {{ kind: 'anonymous' } | { kind: 'bearer', token: string }} [opts.auth]
133
133
  * @param {string} [opts.clientName]
134
134
  * @param {string} [opts.subprotocol] Override the advertised subprotocol.
@@ -153,10 +153,10 @@ export async function connectRedwireWs(opts = {}) {
153
153
  'no global WebSocket in this runtime; pass opts.WebSocketImpl',
154
154
  )
155
155
  }
156
- if (typeof url !== 'string' || !url.startsWith('wss://')) {
156
+ if (typeof url !== 'string' || !(url.startsWith('wss://') || url.startsWith('ws://'))) {
157
157
  throw new RedDBError(
158
- 'WSS_REQUIRED',
159
- `redwire websocket requires a wss:// url, got '${url}'`,
158
+ 'WEBSOCKET_URL_REQUIRED',
159
+ `redwire websocket requires a ws:// or wss:// url, got '${url}'`,
160
160
  )
161
161
  }
162
162