@lobb-js/core 0.13.3 → 0.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobb-js/core",
3
- "version": "0.13.3",
3
+ "version": "0.14.0",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "publishConfig": {
package/src/Lobb.ts CHANGED
@@ -59,7 +59,6 @@ export class Lobb {
59
59
  };
60
60
  }
61
61
 
62
- console.log("starting the app");
63
62
  const lobb = new Lobb();
64
63
  lobb.studio = studio;
65
64
  lobb.tempDatabase = isTempDatabase;
@@ -79,7 +78,7 @@ export class Lobb {
79
78
  lobb.schemaService = await SchemaService.init();
80
79
  const webPort = lobb.configManager.config.web_server.port ?? 3000;
81
80
  const studioPort = await studio.start(webPort);
82
- lobb.webServer = await WebServer.init(studioPort);
81
+ lobb.webServer = await WebServer.init(studio.devPort !== null ? null : studioPort);
83
82
  lobb.collectionControllers = collectionControllers;
84
83
  lobb.transactions = transactions;
85
84
  await lobb.extensionSystem.executeExtensionsInitMethods();
@@ -90,7 +89,7 @@ export class Lobb {
90
89
  // generate types
91
90
  lobb.typesGenerator = await TypesGenerator.init();
92
91
 
93
- console.log("The app is running");
92
+ console.log(`The app is running on http://localhost:${studioPort ?? lobb.webServer.port}`);
94
93
 
95
94
  return lobb;
96
95
  }
@@ -74,53 +74,11 @@ export class WebServer {
74
74
  this.app.all("/studio/*", studioProxy);
75
75
  }
76
76
 
77
- const isDev = process.env.LOBB_MODE !== "prod";
78
- const serveBase = {
77
+ this.server = Bun.serve({
79
78
  port: this.webConfig.port ?? 3000,
80
79
  hostname: this.webConfig.host ?? "0.0.0.0",
81
- };
82
-
83
- // In dev, forward WebSocket upgrade requests to the Vite dev server so that
84
- // Vite's HMR can connect through the Lobb proxy instead of falling back to
85
- // a direct connection (which causes a console warning).
86
- if (isDev && studioPort) {
87
- type WSData = { path: string; upstream?: WebSocket };
88
- this.server = Bun.serve<WSData>({
89
- ...serveBase,
90
- fetch: (req, server) => {
91
- if (req.headers.get("upgrade") === "websocket") {
92
- const url = new URL(req.url);
93
- if (url.pathname.startsWith("/studio")) {
94
- const upgraded = server.upgrade(req, { data: { path: url.pathname + url.search } });
95
- if (upgraded) return undefined;
96
- }
97
- }
98
- return this.app.fetch(req);
99
- },
100
- websocket: {
101
- open(ws) {
102
- const upstream = new WebSocket(`ws://localhost:${studioPort}${ws.data.path.replace(/^\/studio/, "") || "/"}`);
103
- ws.data.upstream = upstream;
104
- upstream.addEventListener("message", (e) => ws.send(e.data as string | ArrayBuffer));
105
- upstream.addEventListener("close", (e) => ws.close(e.code, e.reason));
106
- upstream.addEventListener("error", () => ws.close());
107
- },
108
- message(ws, message) {
109
- if (ws.data.upstream?.readyState === WebSocket.OPEN) {
110
- ws.data.upstream.send(message as string | ArrayBuffer);
111
- }
112
- },
113
- close(ws, code, reason) {
114
- ws.data.upstream?.close(code, reason);
115
- },
116
- },
117
- });
118
- } else {
119
- this.server = Bun.serve({
120
- ...serveBase,
121
- fetch: this.app.fetch,
122
- });
123
- }
80
+ fetch: this.app.fetch,
81
+ });
124
82
  this.port = this.server.port!;
125
83
  }
126
84
  }
@@ -7,6 +7,7 @@ import type { AddressInfo } from "node:net";
7
7
  export class Studio {
8
8
  private studioDir: string;
9
9
  private process: ReturnType<typeof Bun.spawn> | null = null;
10
+ public devPort: number | null = null;
10
11
 
11
12
  constructor(projectDir: string) {
12
13
  // Support flat structure (vite.config.ts at project root) and nested (studio/ subdir).
@@ -21,24 +22,17 @@ export class Studio {
21
22
  }
22
23
 
23
24
  public async start(webPort: number): Promise<number | null> {
25
+ if (this.process) return null;
24
26
  if (!await this.exists()) return null;
25
27
 
26
- const mode = process.env.LOBB_MODE;
27
-
28
- if (mode === "prod") {
29
- const buildDir = `${this.studioDir}/build`;
30
- try {
31
- const s = await stat(buildDir);
32
- if (!s.isDirectory()) throw new Error();
33
- } catch {
34
- throw new Error(
35
- "Studio build not found. Run 'bun run build:studio' before starting in production mode."
36
- );
37
- }
38
- return await this.serveBuild(webPort);
39
- }
28
+ const buildDir = `${this.studioDir}/build`;
29
+ try {
30
+ const s = await stat(buildDir);
31
+ if (s.isDirectory()) return await this.serveBuild(webPort);
32
+ } catch {}
40
33
 
41
- return await this.runDevServer(webPort);
34
+ await this.runDevServer(webPort);
35
+ return this.devPort;
42
36
  }
43
37
 
44
38
  private async exists(): Promise<boolean> {
@@ -51,13 +45,12 @@ export class Studio {
51
45
  }
52
46
 
53
47
  private async serveBuild(webPort: number): Promise<number> {
54
- console.log("Studio: running production build.");
55
48
  const port = await Studio.getFreePort();
56
49
  this.process = Bun.spawn(["bun", "run", "build/index.js"], {
57
50
  cwd: this.studioDir,
58
51
  env: { ...process.env, PORT: String(port) },
59
- stdin: "pipe",
60
- stdout: "inherit",
52
+ stdin: "ignore",
53
+ stdout: "ignore",
61
54
  stderr: "inherit",
62
55
  });
63
56
  await Studio.waitForPort(port);
@@ -65,19 +58,18 @@ export class Studio {
65
58
  return port;
66
59
  }
67
60
 
68
- private async runDevServer(webPort: number): Promise<number> {
69
- const port = await Studio.getFreePort();
70
- console.log("Studio: running dev server.");
71
- this.process = Bun.spawn(["bun", "run", "dev:studio", "--port", String(port)], {
72
- cwd: this.studioDir,
73
- env: { ...process.env },
74
- stdin: "pipe",
75
- stdout: "pipe",
76
- stderr: "inherit",
77
- });
78
- await Studio.waitForPort(port);
79
- console.log(`Studio: http://localhost:${webPort}/studio`);
80
- return port;
61
+ private async runDevServer(webPort: number): Promise<void> {
62
+ this.devPort = await Studio.getAvailablePort(5173);
63
+ this.process = Bun.spawn(
64
+ ["bun", "run", "dev:studio", "--", "--port", String(this.devPort)],
65
+ {
66
+ cwd: this.studioDir,
67
+ stdin: "ignore",
68
+ stdout: "inherit",
69
+ stderr: "inherit",
70
+ env: { ...process.env, LOBB_PORT: String(webPort) },
71
+ }
72
+ );
81
73
  }
82
74
 
83
75
  private static getFreePort(): Promise<number> {
@@ -93,6 +85,24 @@ export class Studio {
93
85
  });
94
86
  }
95
87
 
88
+ // Like Vite's port resolution: try preferred port, increment on conflict.
89
+ // Predictable ports preserve browser sessions across restarts.
90
+ private static getAvailablePort(preferred: number): Promise<number> {
91
+ return new Promise((resolve, reject) => {
92
+ const server = createServer();
93
+ server.listen(preferred, "127.0.0.1", () => {
94
+ server.close((err) => {
95
+ if (err) reject(err);
96
+ else resolve(preferred);
97
+ });
98
+ });
99
+ server.on("error", (err: NodeJS.ErrnoException) => {
100
+ if (err.code === "EADDRINUSE") resolve(Studio.getAvailablePort(preferred + 1));
101
+ else reject(err);
102
+ });
103
+ });
104
+ }
105
+
96
106
  private static async waitForPort(port: number, maxAttempts = 50): Promise<void> {
97
107
  for (let i = 0; i < maxAttempts; i++) {
98
108
  const connected = await new Promise<boolean>((resolve) => {