@jay-framework/dev-server 0.7.0 → 0.8.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 (3) hide show
  1. package/dist/index.d.ts +50 -3
  2. package/dist/index.js +188 -14
  3. package/package.json +12 -12
package/dist/index.d.ts CHANGED
@@ -1,15 +1,61 @@
1
- import { Connect, ViteDevServer } from 'vite';
1
+ import { ViteDevServer, Connect } from 'vite';
2
2
  import { JayRoute } from '@jay-framework/stack-route-scanner';
3
3
  import { RequestHandler } from 'express-serve-static-core';
4
4
  import { JayRollupConfig } from '@jay-framework/rollup-plugin';
5
5
 
6
6
  interface DevServerOptions {
7
- serverBase?: string;
8
- pagesBase?: string;
7
+ publicBaseUrlPath?: string;
8
+ projectRootFolder?: string;
9
+ pagesRootFolder?: string;
9
10
  jayRollupConfig: JayRollupConfig;
10
11
  dontCacheSlowly: boolean;
11
12
  }
12
13
 
14
+ /**
15
+ * Service lifecycle management for the Jay Stack dev-server.
16
+ *
17
+ * Handles loading jay.init.ts, running init/shutdown callbacks,
18
+ * hot reloading services, and graceful shutdown.
19
+ */
20
+
21
+ declare class ServiceLifecycleManager {
22
+ private projectRoot;
23
+ private sourceBase;
24
+ private initFilePath;
25
+ private isInitialized;
26
+ private viteServer;
27
+ constructor(projectRoot: string, sourceBase?: string);
28
+ /**
29
+ * Set the Vite server instance for SSR module loading
30
+ */
31
+ setViteServer(viteServer: ViteDevServer): void;
32
+ /**
33
+ * Finds the jay.init.ts (or .js) file in the source directory.
34
+ * Looks in: {projectRoot}/{sourceBase}/jay.init.{ts,js,mts,mjs}
35
+ */
36
+ private findInitFile;
37
+ /**
38
+ * Initializes services by loading and executing jay.init.ts
39
+ */
40
+ initialize(): Promise<void>;
41
+ /**
42
+ * Shuts down services gracefully with timeout
43
+ */
44
+ shutdown(timeoutMs?: number): Promise<void>;
45
+ /**
46
+ * Hot reload: shutdown, clear caches, re-import, and re-initialize
47
+ */
48
+ reload(): Promise<void>;
49
+ /**
50
+ * Returns the path to the init file if found
51
+ */
52
+ getInitFilePath(): string | null;
53
+ /**
54
+ * Checks if services are initialized
55
+ */
56
+ isReady(): boolean;
57
+ }
58
+
13
59
  interface DevServerRoute {
14
60
  path: string;
15
61
  handler: RequestHandler;
@@ -19,6 +65,7 @@ interface DevServer {
19
65
  server: Connect.Server;
20
66
  viteServer: ViteDevServer;
21
67
  routes: DevServerRoute[];
68
+ lifecycleManager: ServiceLifecycleManager;
22
69
  }
23
70
  declare function mkDevServer(options: DevServerOptions): Promise<DevServer>;
24
71
 
package/dist/index.js CHANGED
@@ -1,8 +1,138 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => {
4
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+ return value;
6
+ };
1
7
  import { createServer } from "vite";
2
8
  import { scanRoutes, routeToExpressRoute } from "@jay-framework/stack-route-scanner";
3
- import { DevSlowlyChangingPhase, loadPageParts, renderFastChangingData, generateClientScript } from "@jay-framework/stack-server-runtime";
9
+ import { runInitCallbacks, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, DevSlowlyChangingPhase, loadPageParts, renderFastChangingData, generateClientScript } from "@jay-framework/stack-server-runtime";
4
10
  import { jayRuntime } from "@jay-framework/vite-plugin";
5
- import path from "node:path";
11
+ import * as path from "node:path";
12
+ import path__default from "node:path";
13
+ import * as fs from "node:fs";
14
+ import { pathToFileURL } from "node:url";
15
+ class ServiceLifecycleManager {
16
+ constructor(projectRoot, sourceBase = "src") {
17
+ __publicField(this, "initFilePath", null);
18
+ __publicField(this, "isInitialized", false);
19
+ __publicField(this, "viteServer", null);
20
+ this.projectRoot = projectRoot;
21
+ this.sourceBase = sourceBase;
22
+ }
23
+ /**
24
+ * Set the Vite server instance for SSR module loading
25
+ */
26
+ setViteServer(viteServer) {
27
+ this.viteServer = viteServer;
28
+ }
29
+ /**
30
+ * Finds the jay.init.ts (or .js) file in the source directory.
31
+ * Looks in: {projectRoot}/{sourceBase}/jay.init.{ts,js,mts,mjs}
32
+ */
33
+ findInitFile() {
34
+ const extensions = [".ts", ".js", ".mts", ".mjs"];
35
+ const baseFilename = "jay.init";
36
+ for (const ext of extensions) {
37
+ const filePath = path.join(this.projectRoot, this.sourceBase, baseFilename + ext);
38
+ if (fs.existsSync(filePath)) {
39
+ return filePath;
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+ /**
45
+ * Initializes services by loading and executing jay.init.ts
46
+ */
47
+ async initialize() {
48
+ if (this.isInitialized) {
49
+ console.warn("[Services] Already initialized, skipping...");
50
+ return;
51
+ }
52
+ this.initFilePath = this.findInitFile();
53
+ if (!this.initFilePath) {
54
+ console.log("[Services] No jay.init.ts found in src/, skipping service initialization");
55
+ return;
56
+ }
57
+ console.log(`[Services] Loading initialization file: ${this.initFilePath}`);
58
+ try {
59
+ if (this.viteServer) {
60
+ await this.viteServer.ssrLoadModule(this.initFilePath);
61
+ } else {
62
+ const fileUrl = pathToFileURL(this.initFilePath).href;
63
+ await import(fileUrl);
64
+ }
65
+ await runInitCallbacks();
66
+ this.isInitialized = true;
67
+ console.log("[Services] Initialization complete");
68
+ } catch (error) {
69
+ console.error("[Services] Failed to initialize:", error);
70
+ throw error;
71
+ }
72
+ }
73
+ /**
74
+ * Shuts down services gracefully with timeout
75
+ */
76
+ async shutdown(timeoutMs = 5e3) {
77
+ if (!this.isInitialized) {
78
+ return;
79
+ }
80
+ console.log("[Services] Shutting down...");
81
+ try {
82
+ await Promise.race([
83
+ runShutdownCallbacks(),
84
+ new Promise(
85
+ (_, reject) => setTimeout(() => reject(new Error("Shutdown timeout")), timeoutMs)
86
+ )
87
+ ]);
88
+ console.log("[Services] Shutdown complete");
89
+ } catch (error) {
90
+ if (error.message === "Shutdown timeout") {
91
+ console.warn("[Services] Shutdown timed out after", timeoutMs, "ms");
92
+ } else {
93
+ console.error("[Services] Shutdown error:", error);
94
+ }
95
+ } finally {
96
+ this.isInitialized = false;
97
+ }
98
+ }
99
+ /**
100
+ * Hot reload: shutdown, clear caches, re-import, and re-initialize
101
+ */
102
+ async reload() {
103
+ if (!this.initFilePath) {
104
+ console.log("[Services] No init file to reload");
105
+ return;
106
+ }
107
+ console.log("[Services] Reloading services...");
108
+ await this.shutdown();
109
+ clearLifecycleCallbacks();
110
+ clearServiceRegistry();
111
+ if (this.viteServer) {
112
+ const moduleNode = this.viteServer.moduleGraph.getModuleById(this.initFilePath);
113
+ if (moduleNode) {
114
+ await this.viteServer.moduleGraph.invalidateModule(moduleNode);
115
+ }
116
+ } else {
117
+ delete require.cache[require.resolve(this.initFilePath)];
118
+ }
119
+ this.isInitialized = false;
120
+ await this.initialize();
121
+ console.log("[Services] Reload complete");
122
+ }
123
+ /**
124
+ * Returns the path to the init file if found
125
+ */
126
+ getInitFilePath() {
127
+ return this.initFilePath;
128
+ }
129
+ /**
130
+ * Checks if services are initialized
131
+ */
132
+ isReady() {
133
+ return this.isInitialized;
134
+ }
135
+ }
6
136
  async function initRoutes(pagesBaseFolder) {
7
137
  return await scanRoutes(pagesBaseFolder, {
8
138
  jayHtmlFilename: "page.jay-html",
@@ -10,12 +140,14 @@ async function initRoutes(pagesBaseFolder) {
10
140
  });
11
141
  }
12
142
  function defaults(options) {
13
- const serverBase = options.serverBase || process.env.BASE || "/";
14
- const pagesBase = path.resolve(serverBase, options.pagesBase || "./src/pages");
15
- const tsConfigFilePath = options.jayRollupConfig.tsConfigFilePath || path.resolve(serverBase, "./tsconfig.json");
143
+ const publicBaseUrlPath = options.publicBaseUrlPath || process.env.BASE || "/";
144
+ const projectRootFolder = options.projectRootFolder || ".";
145
+ const pagesRootFolder = path__default.resolve(projectRootFolder, options.pagesRootFolder || "./src/pages");
146
+ const tsConfigFilePath = options.jayRollupConfig.tsConfigFilePath || path__default.resolve(projectRootFolder, "./tsconfig.json");
16
147
  return {
17
- serverBase,
18
- pagesBase,
148
+ publicBaseUrlPath,
149
+ pagesRootFolder,
150
+ projectRootFolder,
19
151
  dontCacheSlowly: options.dontCacheSlowly,
20
152
  jayRollupConfig: {
21
153
  ...options.jayRollupConfig || {},
@@ -35,7 +167,7 @@ function mkRoute(route, vite, slowlyPhase, options) {
35
167
  const path2 = routeToExpressRoute(route);
36
168
  const handler = async (req, res) => {
37
169
  try {
38
- const url = req.originalUrl.replace(options.serverBase, "");
170
+ const url = req.originalUrl.replace(options.publicBaseUrlPath, "");
39
171
  const pageParams = req.params;
40
172
  const pageProps = {
41
173
  language: "en",
@@ -45,7 +177,7 @@ function mkRoute(route, vite, slowlyPhase, options) {
45
177
  const pageParts = await loadPageParts(
46
178
  vite,
47
179
  route,
48
- options.pagesBase,
180
+ options.pagesRootFolder,
49
181
  options.jayRollupConfig
50
182
  );
51
183
  if (pageParts.val) {
@@ -94,15 +226,25 @@ function mkRoute(route, vite, slowlyPhase, options) {
94
226
  return { path: path2, handler, fsRoute: route };
95
227
  }
96
228
  async function mkDevServer(options) {
97
- const { serverBase, pagesBase, jayRollupConfig, dontCacheSlowly } = defaults(options);
229
+ const { publicBaseUrlPath, pagesRootFolder, projectRootFolder, jayRollupConfig, dontCacheSlowly } = defaults(options);
230
+ const lifecycleManager = new ServiceLifecycleManager(projectRootFolder);
231
+ setupGracefulShutdown(lifecycleManager);
98
232
  const vite = await createServer({
99
233
  server: { middlewareMode: true },
100
234
  plugins: [jayRuntime(jayRollupConfig)],
101
235
  appType: "custom",
102
- base: serverBase,
103
- root: pagesBase
236
+ base: publicBaseUrlPath,
237
+ root: pagesRootFolder,
238
+ ssr: {
239
+ // Mark stack-server-runtime as external so Vite uses Node's require
240
+ // This ensures jay.init.ts and dev-server share the same module instance
241
+ external: ["@jay-framework/stack-server-runtime"]
242
+ }
104
243
  });
105
- const routes = await initRoutes(pagesBase);
244
+ lifecycleManager.setViteServer(vite);
245
+ await lifecycleManager.initialize();
246
+ setupServiceHotReload(vite, lifecycleManager);
247
+ const routes = await initRoutes(pagesRootFolder);
106
248
  const slowlyPhase = new DevSlowlyChangingPhase(dontCacheSlowly);
107
249
  const devServerRoutes = routes.map(
108
250
  (route) => mkRoute(route, vite, slowlyPhase, options)
@@ -110,9 +252,41 @@ async function mkDevServer(options) {
110
252
  return {
111
253
  server: vite.middlewares,
112
254
  viteServer: vite,
113
- routes: devServerRoutes
255
+ routes: devServerRoutes,
256
+ lifecycleManager
114
257
  };
115
258
  }
259
+ function setupGracefulShutdown(lifecycleManager) {
260
+ const shutdown = async (signal) => {
261
+ console.log(`
262
+ ${signal} received, shutting down gracefully...`);
263
+ await lifecycleManager.shutdown();
264
+ process.exit(0);
265
+ };
266
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
267
+ process.on("SIGINT", () => shutdown("SIGINT"));
268
+ }
269
+ function setupServiceHotReload(vite, lifecycleManager) {
270
+ const initFilePath = lifecycleManager.getInitFilePath();
271
+ if (!initFilePath) {
272
+ return;
273
+ }
274
+ vite.watcher.add(initFilePath);
275
+ vite.watcher.on("change", async (changedPath) => {
276
+ if (changedPath === initFilePath) {
277
+ console.log("[Services] jay.init.ts changed, reloading services...");
278
+ try {
279
+ await lifecycleManager.reload();
280
+ vite.ws.send({
281
+ type: "full-reload",
282
+ path: "*"
283
+ });
284
+ } catch (error) {
285
+ console.error("[Services] Failed to reload services:", error);
286
+ }
287
+ }
288
+ });
289
+ }
116
290
  export {
117
291
  mkDevServer
118
292
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/dev-server",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -22,20 +22,20 @@
22
22
  "test:watch": "vitest"
23
23
  },
24
24
  "dependencies": {
25
- "@jay-framework/compiler-shared": "^0.7.0",
26
- "@jay-framework/component": "^0.7.0",
27
- "@jay-framework/fullstack-component": "^0.7.0",
28
- "@jay-framework/runtime": "^0.7.0",
29
- "@jay-framework/stack-client-runtime": "^0.7.0",
30
- "@jay-framework/stack-route-scanner": "^0.7.0",
31
- "@jay-framework/stack-server-runtime": "^0.7.0",
32
- "@jay-framework/vite-plugin": "^0.7.0",
25
+ "@jay-framework/compiler-shared": "^0.8.0",
26
+ "@jay-framework/component": "^0.8.0",
27
+ "@jay-framework/fullstack-component": "^0.8.0",
28
+ "@jay-framework/runtime": "^0.8.0",
29
+ "@jay-framework/stack-client-runtime": "^0.8.0",
30
+ "@jay-framework/stack-route-scanner": "^0.8.0",
31
+ "@jay-framework/stack-server-runtime": "^0.8.0",
32
+ "@jay-framework/vite-plugin": "^0.8.0",
33
33
  "vite": "^5.0.11"
34
34
  },
35
35
  "devDependencies": {
36
- "@jay-framework/dev-environment": "^0.7.0",
37
- "@jay-framework/jay-cli": "^0.7.0",
38
- "@jay-framework/stack-client-runtime": "^0.7.0",
36
+ "@jay-framework/dev-environment": "^0.8.0",
37
+ "@jay-framework/jay-cli": "^0.8.0",
38
+ "@jay-framework/stack-client-runtime": "^0.8.0",
39
39
  "@types/express": "^5.0.2",
40
40
  "@types/node": "^22.15.21",
41
41
  "nodemon": "^3.0.3",