@ultimat3/realtime 19.1.2 → 19.2.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/package.json +3 -3
  2. package/src/sync-listen.ts +16 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/realtime",
3
- "version": "19.1.2",
3
+ "version": "19.2.0",
4
4
  "description": "Three-tier realtime: channels, live queries, local-first sync — one protocol, one mutator shape",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,8 +36,8 @@
36
36
  "test": "bun test"
37
37
  },
38
38
  "dependencies": {
39
- "@ultimat3/core": "19.1.2",
40
- "@ultimat3/query": "19.1.2",
39
+ "@ultimat3/core": "19.2.0",
40
+ "@ultimat3/query": "19.2.0",
41
41
  "nats": "2.29.3"
42
42
  }
43
43
  }
@@ -7,6 +7,19 @@ import type { SyncNode } from './sync-node';
7
7
 
8
8
  export interface ListenOptions {
9
9
  readonly port?: number;
10
+ /**
11
+ * The interface to bind. Defaults to `0.0.0.0` — Bun's default, and the right one for a
12
+ * container, where a process bound to loopback is unreachable from the port mapping.
13
+ *
14
+ * A CALLER THAT BINDS ITS WEB ROLE TO LOOPBACK MUST PASS IT HERE TOO. `x dev` did not, and the
15
+ * result was a laptop serving its live database to the LAN: the web role took
16
+ * `DEV_BINDING.hostname` (`localhost`, and its own docstring says why — "so a laptop on a café
17
+ * network is not serving the app to the café"), while the sync node on the very next port took
18
+ * this default and answered on every interface. The socket carries `snapshot` and `patch`
19
+ * frames for every live query the app has registered, so the intent the web role states was
20
+ * defeated by the listener beside it.
21
+ */
22
+ readonly hostname?: string;
10
23
  }
11
24
 
12
25
  export interface SyncListener {
@@ -22,6 +35,9 @@ export interface SyncListener {
22
35
  export function listenSyncNode(node: SyncNode, options: ListenOptions = {}): SyncListener {
23
36
  const server = Bun.serve({
24
37
  port: finiteOption('listenSyncNode', 'port', options.port ?? 3001),
38
+ // Spread, not `hostname: options.hostname` — Bun reads an explicit `undefined` as a request
39
+ // and not as an absence, and the default it applies to a missing key is the one we want.
40
+ ...(options.hostname === undefined ? {} : { hostname: options.hostname }),
25
41
  fetch: node.fetch,
26
42
  websocket: node.websocket,
27
43
  });