@moriajs/core 0.1.1 → 0.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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @moriajs/core@0.1.1 build C:\Codes\node\2026\github\moriajs\packages\core
2
+ > @moriajs/core@0.2.0 build C:\Codes\node\2026\github\moriajs\packages\core
3
3
  > tsc
4
4
 
package/dist/app.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type FastifyInstance, type FastifyServerOptions } from 'fastify';
2
2
  import { type MoriaConfig } from './config.js';
3
3
  import { type MoriaPlugin } from './plugins.js';
4
+ import type { ViteDevServer } from 'vite';
4
5
  /**
5
6
  * Options for creating a MoriaJS application.
6
7
  */
@@ -17,6 +18,8 @@ export interface MoriaAppOptions {
17
18
  export interface MoriaApp {
18
19
  /** The underlying Fastify instance */
19
20
  server: FastifyInstance;
21
+ /** The Vite dev server (only in development mode) */
22
+ vite?: ViteDevServer;
20
23
  /** Register a MoriaJS plugin */
21
24
  use: (plugin: MoriaPlugin) => Promise<void>;
22
25
  /** Start the server */
@@ -34,7 +37,7 @@ export interface MoriaApp {
34
37
  * ```ts
35
38
  * import { createApp } from '@moriajs/core';
36
39
  *
37
- * const app = await createApp();
40
+ * const app = await createApp({ config: { mode: 'development' } });
38
41
  * await app.listen({ port: 3000 });
39
42
  * ```
40
43
  */
package/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,KAAK,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAKnF,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,mDAAmD;IACnD,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9B,wCAAwC;IACxC,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACzC;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACrB,sCAAsC;IACtC,MAAM,EAAE,eAAe,CAAC;IACxB,gCAAgC;IAChC,GAAG,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,uBAAuB;IACvB,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACxE,sBAAsB;IACtB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0DhF"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,KAAK,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAOnF,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,mDAAmD;IACnD,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9B,wCAAwC;IACxC,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACzC;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACrB,sCAAsC;IACtC,MAAM,EAAE,eAAe,CAAC;IACxB,qDAAqD;IACrD,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,gCAAgC;IAChC,GAAG,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,uBAAuB;IACvB,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACxE,sBAAsB;IACtB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4EhF"}
package/dist/app.js CHANGED
@@ -3,6 +3,10 @@ import cors from '@fastify/cors';
3
3
  import cookie from '@fastify/cookie';
4
4
  import compress from '@fastify/compress';
5
5
  import helmet from '@fastify/helmet';
6
+ import path from 'node:path';
7
+ import fs from 'node:fs';
8
+ import { createViteDevMiddleware, serveProductionAssets } from './vite.js';
9
+ import { registerRoutes } from './router.js';
6
10
  /**
7
11
  * Create a new MoriaJS application.
8
12
  *
@@ -10,19 +14,21 @@ import helmet from '@fastify/helmet';
10
14
  * ```ts
11
15
  * import { createApp } from '@moriajs/core';
12
16
  *
13
- * const app = await createApp();
17
+ * const app = await createApp({ config: { mode: 'development' } });
14
18
  * await app.listen({ port: 3000 });
15
19
  * ```
16
20
  */
17
21
  export async function createApp(options = {}) {
18
22
  const config = options.config ?? {};
23
+ const mode = config.mode ?? (process.env.NODE_ENV === 'production' ? 'production' : 'development');
24
+ const rootDir = config.rootDir ?? process.cwd();
19
25
  const port = config.server?.port ?? 3000;
20
26
  const host = config.server?.host ?? '0.0.0.0';
21
27
  // Create Fastify instance with sensible defaults
22
28
  const server = Fastify({
23
29
  logger: {
24
30
  level: config.server?.logLevel ?? 'info',
25
- transport: process.env.NODE_ENV !== 'production'
31
+ transport: mode !== 'production'
26
32
  ? { target: 'pino-pretty', options: { colorize: true } }
27
33
  : undefined,
28
34
  },
@@ -37,14 +43,28 @@ export async function createApp(options = {}) {
37
43
  await server.register(compress);
38
44
  await server.register(helmet, {
39
45
  // Relax CSP in development for Vite HMR
40
- contentSecurityPolicy: process.env.NODE_ENV === 'production',
46
+ contentSecurityPolicy: mode === 'production',
41
47
  });
42
48
  // Health check route
43
49
  server.get('/health', async () => ({ status: 'ok', timestamp: new Date().toISOString() }));
50
+ // ─── Vite Integration ────────────────────────────────────
51
+ let vite;
52
+ if (mode === 'development') {
53
+ vite = await createViteDevMiddleware(server, { ...config, rootDir });
54
+ }
55
+ else {
56
+ await serveProductionAssets(server, { ...config, rootDir });
57
+ }
58
+ // ─── File-Based Routing ──────────────────────────────────
59
+ const routesDir = path.resolve(rootDir, config.routes?.dir ?? 'src/routes');
60
+ if (fs.existsSync(routesDir)) {
61
+ await registerRoutes(server, routesDir, { mode, config });
62
+ }
44
63
  // Plugin registry
45
64
  const plugins = [];
46
65
  const app = {
47
66
  server,
67
+ vite,
48
68
  async use(plugin) {
49
69
  plugins.push(plugin);
50
70
  await plugin.register({ server, config });
package/dist/app.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,OAA4D,MAAM,SAAS,CAAC;AACnF,OAAO,IAAI,MAAM,eAAe,CAAC;AACjC,OAAO,MAAM,MAAM,iBAAiB,CAAC;AACrC,OAAO,QAAQ,MAAM,mBAAmB,CAAC;AACzC,OAAO,MAAM,MAAM,iBAAiB,CAAC;AA6BrC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,UAA2B,EAAE;IACzD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI,CAAC;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,SAAS,CAAC;IAE9C,iDAAiD;IACjD,MAAM,MAAM,GAAG,OAAO,CAAC;QACnB,MAAM,EAAE;YACJ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,IAAI,MAAM;YACxC,SAAS,EACL,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;gBACjC,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;gBACxD,CAAC,CAAC,SAAS;SACtB;QACD,GAAG,OAAO,CAAC,cAAc;KAC5B,CAAC,CAAC;IAEH,wBAAwB;IACxB,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,IAAI;QAC3C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,WAAW,IAAI,IAAI;KACxD,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE;QAC1B,wCAAwC;QACxC,qBAAqB,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;KAC/D,CAAC,CAAC;IAEH,qBAAqB;IACrB,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;IAE3F,kBAAkB;IAClB,MAAM,OAAO,GAAkB,EAAE,CAAC;IAElC,MAAM,GAAG,GAAa;QAClB,MAAM;QAEN,KAAK,CAAC,GAAG,CAAC,MAAmB;YACzB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACrB,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9C,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,aAAa;YACtB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;gBAC7B,IAAI,EAAE,aAAa,EAAE,IAAI,IAAI,IAAI;gBACjC,IAAI,EAAE,aAAa,EAAE,IAAI,IAAI,IAAI;aACpC,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,KAAK,CAAC,KAAK;YACP,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACzB,CAAC;KACJ,CAAC;IAEF,OAAO,GAAG,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
package/dist/config.d.ts CHANGED
@@ -3,6 +3,10 @@
3
3
  * Used in `moria.config.ts` files in user projects.
4
4
  */
5
5
  export interface MoriaConfig {
6
+ /** Application mode */
7
+ mode?: 'development' | 'production';
8
+ /** Project root directory (auto-detected if not set) */
9
+ rootDir?: string;
6
10
  /** Server configuration */
7
11
  server?: {
8
12
  /** Port to listen on (default: 3000) */
@@ -41,6 +45,13 @@ export interface MoriaConfig {
41
45
  vite?: {
42
46
  /** Path to Vite config file */
43
47
  configFile?: string;
48
+ /** Client entry point (default: '/src/entry-client.ts') */
49
+ clientEntry?: string;
50
+ };
51
+ /** File-based routing configuration */
52
+ routes?: {
53
+ /** Routes directory relative to rootDir (default: 'src/routes') */
54
+ dir?: string;
44
55
  };
45
56
  }
46
57
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,2BAA2B;IAC3B,MAAM,CAAC,EAAE;QACL,wCAAwC;QACxC,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,2CAA2C;QAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,kCAAkC;QAClC,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;QAC9E,yBAAyB;QACzB,IAAI,CAAC,EAAE;YACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;YACrC,WAAW,CAAC,EAAE,OAAO,CAAC;SACzB,CAAC;KACL,CAAC;IAEF,6BAA6B;IAC7B,QAAQ,CAAC,EAAE;QACP,kDAAkD;QAClD,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,qBAAqB;QACrB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,+CAA+C;QAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IAEF,mCAAmC;IACnC,IAAI,CAAC,EAAE;QACH,qBAAqB;QACrB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,2CAA2C;QAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,gCAAgC;QAChC,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,yCAAyC;QACzC,aAAa,CAAC,EAAE,OAAO,CAAC;KAC3B,CAAC;IAEF,iCAAiC;IACjC,IAAI,CAAC,EAAE;QACH,+BAA+B;QAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACL;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAE7D"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,uBAAuB;IACvB,IAAI,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IAEpC,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,2BAA2B;IAC3B,MAAM,CAAC,EAAE;QACL,wCAAwC;QACxC,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,2CAA2C;QAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,kCAAkC;QAClC,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;QAC9E,yBAAyB;QACzB,IAAI,CAAC,EAAE;YACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;YACrC,WAAW,CAAC,EAAE,OAAO,CAAC;SACzB,CAAC;KACL,CAAC;IAEF,6BAA6B;IAC7B,QAAQ,CAAC,EAAE;QACP,kDAAkD;QAClD,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,qBAAqB;QACrB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,+CAA+C;QAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IAEF,mCAAmC;IACnC,IAAI,CAAC,EAAE;QACH,qBAAqB;QACrB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,2CAA2C;QAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,gCAAgC;QAChC,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,yCAAyC;QACzC,aAAa,CAAC,EAAE,OAAO,CAAC;KAC3B,CAAC;IAEF,iCAAiC;IACjC,IAAI,CAAC,EAAE;QACH,+BAA+B;QAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,2DAA2D;QAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IAEF,uCAAuC;IACvC,MAAM,CAAC,EAAE;QACL,mEAAmE;QACnE,GAAG,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACL;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAE7D"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAiDA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAAC,MAAmB;IAC5C,OAAO,MAAM,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AA+DA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAAC,MAAmB;IAC5C,OAAO,MAAM,CAAC;AAClB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -2,12 +2,15 @@
2
2
  * @moriajs/core
3
3
  *
4
4
  * Framework core: Fastify server factory with sensible defaults,
5
- * plugin registration, and configuration system.
5
+ * plugin registration, Vite integration, and file-based routing.
6
6
  */
7
7
  export { createApp } from './app.js';
8
8
  export { defineConfig } from './config.js';
9
9
  export { defineMoriaPlugin } from './plugins.js';
10
+ export { createViteDevMiddleware, serveProductionAssets, getHtmlScripts } from './vite.js';
11
+ export { scanRoutes, registerRoutes, filePathToUrlPath } from './router.js';
10
12
  export type { MoriaApp, MoriaAppOptions } from './app.js';
11
13
  export type { MoriaConfig } from './config.js';
12
14
  export type { MoriaPlugin, MoriaPluginContext } from './plugins.js';
15
+ export type { RouteEntry, RouteHandler } from './router.js';
13
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE5E,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACpE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -2,9 +2,11 @@
2
2
  * @moriajs/core
3
3
  *
4
4
  * Framework core: Fastify server factory with sensible defaults,
5
- * plugin registration, and configuration system.
5
+ * plugin registration, Vite integration, and file-based routing.
6
6
  */
7
7
  export { createApp } from './app.js';
8
8
  export { defineConfig } from './config.js';
9
9
  export { defineMoriaPlugin } from './plugins.js';
10
+ export { createViteDevMiddleware, serveProductionAssets, getHtmlScripts } from './vite.js';
11
+ export { scanRoutes, registerRoutes, filePathToUrlPath } from './router.js';
10
12
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * File-based routing for MoriaJS.
3
+ *
4
+ * Scans `src/routes/` for route files and auto-registers them with Fastify.
5
+ *
6
+ * Convention:
7
+ * src/routes/api/hello.ts → GET /api/hello (API handler)
8
+ * src/routes/api/users/[id].ts → GET /api/users/:id (API handler)
9
+ * src/routes/pages/index.ts → GET / (SSR page)
10
+ * src/routes/pages/about.ts → GET /about (SSR page)
11
+ *
12
+ * API routes export named HTTP method functions:
13
+ * export function GET(request, reply) { ... }
14
+ *
15
+ * Page routes export a Mithril component + optional data loader:
16
+ * export default { view() { return m('h1', 'Hello') } }
17
+ * export async function getServerData(request) { return { user: ... } }
18
+ */
19
+ import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
20
+ import type { MoriaConfig } from './config.js';
21
+ /** Supported HTTP methods in route files. */
22
+ declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
23
+ type HttpMethod = (typeof HTTP_METHODS)[number];
24
+ /** Handler function exported from a route file. */
25
+ export type RouteHandler = (request: FastifyRequest, reply: FastifyReply) => unknown | Promise<unknown>;
26
+ /** Data loader for page routes — runs on server before render. */
27
+ export type GetServerData = (request: FastifyRequest) => unknown | Promise<unknown>;
28
+ /** Discovered route entry. */
29
+ export interface RouteEntry {
30
+ /** File path relative to routes dir */
31
+ filePath: string;
32
+ /** URL path pattern (e.g., /api/users/:id) */
33
+ urlPath: string;
34
+ /** Route type: 'api' or 'page' */
35
+ type: 'api' | 'page';
36
+ /** HTTP method → handler map (API routes) */
37
+ methods: Partial<Record<Lowercase<HttpMethod>, RouteHandler>>;
38
+ /** Mithril component (page routes only) */
39
+ component?: unknown;
40
+ /** Server data loader (page routes only) */
41
+ getServerData?: GetServerData;
42
+ }
43
+ /**
44
+ * Options for route registration.
45
+ */
46
+ export interface RegisterRoutesOptions {
47
+ /** Application mode */
48
+ mode?: 'development' | 'production';
49
+ /** MoriaJS config for renderer options */
50
+ config?: Partial<MoriaConfig>;
51
+ }
52
+ /**
53
+ * Convert a file path to a URL path.
54
+ *
55
+ * - Strips file extension
56
+ * - Converts `[param]` → `:param`
57
+ * - Converts `[...slug]` → `*`
58
+ * - Converts `index` → `/`
59
+ *
60
+ * @example
61
+ * filePathToUrlPath('api/users/[id].ts') → '/api/users/:id'
62
+ * filePathToUrlPath('pages/index.ts') → '/'
63
+ * filePathToUrlPath('pages/about.ts') → '/about'
64
+ */
65
+ export declare function filePathToUrlPath(filePath: string): string;
66
+ /**
67
+ * Scan a directory for route files and return discovered routes.
68
+ *
69
+ * @param routesDir - Absolute path to the routes directory (e.g., `<project>/src/routes`)
70
+ */
71
+ export declare function scanRoutes(routesDir: string): Promise<RouteEntry[]>;
72
+ /**
73
+ * Register discovered routes with a Fastify server.
74
+ *
75
+ * Page routes with Mithril components are auto-wrapped with SSR rendering.
76
+ * API routes are registered directly as Fastify handlers.
77
+ */
78
+ export declare function registerRoutes(server: FastifyInstance, routesDir: string, options?: RegisterRoutesOptions): Promise<RouteEntry[]>;
79
+ export {};
80
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,6CAA6C;AAC7C,QAAA,MAAM,YAAY,uEAAwE,CAAC;AAC3F,KAAK,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhD,mDAAmD;AACnD,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAExG,kEAAkE;AAClE,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpF,8BAA8B;AAC9B,MAAM,WAAW,UAAU;IACvB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IACrB,6CAA6C;IAC7C,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;IAC9D,2CAA2C;IAC3C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4CAA4C;IAC5C,aAAa,CAAC,EAAE,aAAa,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IAClC,uBAAuB;IACvB,IAAI,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IACpC,0CAA0C;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAkC1D;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAgGzE;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAChC,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,qBAA0B,GACpC,OAAO,CAAC,UAAU,EAAE,CAAC,CAsDvB"}
package/dist/router.js ADDED
@@ -0,0 +1,208 @@
1
+ /**
2
+ * File-based routing for MoriaJS.
3
+ *
4
+ * Scans `src/routes/` for route files and auto-registers them with Fastify.
5
+ *
6
+ * Convention:
7
+ * src/routes/api/hello.ts → GET /api/hello (API handler)
8
+ * src/routes/api/users/[id].ts → GET /api/users/:id (API handler)
9
+ * src/routes/pages/index.ts → GET / (SSR page)
10
+ * src/routes/pages/about.ts → GET /about (SSR page)
11
+ *
12
+ * API routes export named HTTP method functions:
13
+ * export function GET(request, reply) { ... }
14
+ *
15
+ * Page routes export a Mithril component + optional data loader:
16
+ * export default { view() { return m('h1', 'Hello') } }
17
+ * export async function getServerData(request) { return { user: ... } }
18
+ */
19
+ import { glob } from 'glob';
20
+ import path from 'node:path';
21
+ import { pathToFileURL } from 'node:url';
22
+ /** Supported HTTP methods in route files. */
23
+ const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
24
+ /**
25
+ * Convert a file path to a URL path.
26
+ *
27
+ * - Strips file extension
28
+ * - Converts `[param]` → `:param`
29
+ * - Converts `[...slug]` → `*`
30
+ * - Converts `index` → `/`
31
+ *
32
+ * @example
33
+ * filePathToUrlPath('api/users/[id].ts') → '/api/users/:id'
34
+ * filePathToUrlPath('pages/index.ts') → '/'
35
+ * filePathToUrlPath('pages/about.ts') → '/about'
36
+ */
37
+ export function filePathToUrlPath(filePath) {
38
+ // Remove extension
39
+ let route = filePath.replace(/\.(ts|js|mts|mjs)$/, '');
40
+ // Normalize separators
41
+ route = route.replace(/\\/g, '/');
42
+ // Convert [param] → :param
43
+ route = route.replace(/\[([^\].]+)\]/g, ':$1');
44
+ // Convert [...slug] → *
45
+ route = route.replace(/\[\.\.\.([^\]]+)\]/g, '*');
46
+ // Handle pages prefix — strip "pages" and make root-relative
47
+ if (route.startsWith('pages/')) {
48
+ route = route.slice(5); // remove "pages"
49
+ }
50
+ // Handle api prefix — keep "api"
51
+ // (no transformation needed)
52
+ // Handle index files → parent path
53
+ route = route.replace(/\/index$/, '');
54
+ // Ensure leading slash
55
+ if (!route.startsWith('/')) {
56
+ route = '/' + route;
57
+ }
58
+ // Root case
59
+ if (route === '') {
60
+ route = '/';
61
+ }
62
+ return route;
63
+ }
64
+ /**
65
+ * Scan a directory for route files and return discovered routes.
66
+ *
67
+ * @param routesDir - Absolute path to the routes directory (e.g., `<project>/src/routes`)
68
+ */
69
+ export async function scanRoutes(routesDir) {
70
+ const pattern = '**/*.{ts,js,mts,mjs}';
71
+ const files = await glob(pattern, {
72
+ cwd: routesDir,
73
+ posix: true,
74
+ ignore: ['**/_*', '**/*.d.ts', '**/*.test.*', '**/*.spec.*'],
75
+ });
76
+ const routes = [];
77
+ for (const file of files) {
78
+ const urlPath = filePathToUrlPath(file);
79
+ const type = file.startsWith('api/') ? 'api' : 'page';
80
+ const absolutePath = path.resolve(routesDir, file);
81
+ const fileUrl = pathToFileURL(absolutePath).href;
82
+ // Dynamically import the route module
83
+ let mod;
84
+ try {
85
+ mod = await import(fileUrl);
86
+ }
87
+ catch (err) {
88
+ console.warn(`[moria] Failed to load route: ${file}`, err);
89
+ continue;
90
+ }
91
+ if (type === 'page') {
92
+ // ─── Page route: expects default Mithril component ───────
93
+ const component = mod.default;
94
+ const getServerData = typeof mod.getServerData === 'function'
95
+ ? mod.getServerData
96
+ : undefined;
97
+ // Page routes can also export raw HTTP handlers (backward compat)
98
+ if (component && typeof component === 'object' && 'view' in component) {
99
+ routes.push({
100
+ filePath: file,
101
+ urlPath,
102
+ type: 'page',
103
+ methods: {},
104
+ component,
105
+ getServerData,
106
+ });
107
+ continue;
108
+ }
109
+ // Fallback: if default export is a function, treat as API-style handler
110
+ if (typeof component === 'function') {
111
+ routes.push({
112
+ filePath: file,
113
+ urlPath,
114
+ type: 'page',
115
+ methods: { get: component },
116
+ });
117
+ continue;
118
+ }
119
+ // Also check for named HTTP method exports
120
+ const methods = {};
121
+ for (const method of HTTP_METHODS) {
122
+ const handler = mod[method] ?? mod[method.toLowerCase()];
123
+ if (typeof handler === 'function') {
124
+ methods[method.toLowerCase()] = handler;
125
+ }
126
+ }
127
+ if (Object.keys(methods).length > 0) {
128
+ routes.push({ filePath: file, urlPath, type: 'page', methods });
129
+ continue;
130
+ }
131
+ console.warn(`[moria] Page route has no component or handlers: ${file}`);
132
+ }
133
+ else {
134
+ // ─── API route: expects named HTTP method exports ────────
135
+ const methods = {};
136
+ for (const method of HTTP_METHODS) {
137
+ const handler = mod[method] ?? mod[method.toLowerCase()];
138
+ if (typeof handler === 'function') {
139
+ methods[method.toLowerCase()] = handler;
140
+ }
141
+ }
142
+ // Also support default export as GET handler
143
+ if (typeof mod.default === 'function' && !methods.get) {
144
+ methods.get = mod.default;
145
+ }
146
+ if (Object.keys(methods).length === 0) {
147
+ console.warn(`[moria] Route file has no handlers: ${file}`);
148
+ continue;
149
+ }
150
+ routes.push({ filePath: file, urlPath, type: 'api', methods });
151
+ }
152
+ }
153
+ return routes;
154
+ }
155
+ /**
156
+ * Register discovered routes with a Fastify server.
157
+ *
158
+ * Page routes with Mithril components are auto-wrapped with SSR rendering.
159
+ * API routes are registered directly as Fastify handlers.
160
+ */
161
+ export async function registerRoutes(server, routesDir, options = {}) {
162
+ const routes = await scanRoutes(routesDir);
163
+ const mode = options.mode ?? 'development';
164
+ const config = options.config ?? {};
165
+ for (const route of routes) {
166
+ // ─── Page route with Mithril component → SSR handler ─────
167
+ if (route.component) {
168
+ const component = route.component;
169
+ const getServerData = route.getServerData;
170
+ const clientEntry = config.vite?.clientEntry ?? '/src/entry-client.ts';
171
+ server.route({
172
+ method: 'GET',
173
+ url: route.urlPath,
174
+ handler: async (request, reply) => {
175
+ // Load server data if available
176
+ let initialData;
177
+ if (getServerData) {
178
+ initialData = (await getServerData(request));
179
+ }
180
+ // Dynamic import of renderer (avoids circular deps)
181
+ const { renderToString } = await import('@moriajs/renderer');
182
+ const html = await renderToString(component, {
183
+ title: component.title ?? 'MoriaJS App',
184
+ initialData,
185
+ mode,
186
+ clientEntry,
187
+ });
188
+ reply.type('text/html');
189
+ return html;
190
+ },
191
+ });
192
+ server.log.info(`Page: GET ${route.urlPath} → ${route.filePath} (SSR)`);
193
+ continue;
194
+ }
195
+ // ─── API / raw handler routes ────────────────────────────
196
+ for (const [method, handler] of Object.entries(route.methods)) {
197
+ server.route({
198
+ method: method.toUpperCase(),
199
+ url: route.urlPath,
200
+ handler: handler,
201
+ });
202
+ server.log.info(`Route: ${method.toUpperCase()} ${route.urlPath} → ${route.filePath}`);
203
+ }
204
+ }
205
+ server.log.info(`Registered ${routes.length} file-based route(s)`);
206
+ return routes;
207
+ }
208
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
package/dist/vite.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Vite integration for MoriaJS.
3
+ *
4
+ * Provides Vite dev server in middleware mode (development)
5
+ * and static file serving for production builds.
6
+ */
7
+ import type { FastifyInstance } from 'fastify';
8
+ import type { ViteDevServer } from 'vite';
9
+ import { type MoriaConfig } from './config.js';
10
+ /**
11
+ * Attach Vite dev server to Fastify in middleware mode.
12
+ * This enables HMR and on-the-fly module transformation.
13
+ */
14
+ export declare function createViteDevMiddleware(server: FastifyInstance, config?: Partial<MoriaConfig>): Promise<ViteDevServer>;
15
+ /**
16
+ * Serve production-built client assets via @fastify/static.
17
+ */
18
+ export declare function serveProductionAssets(server: FastifyInstance, config?: Partial<MoriaConfig>): Promise<void>;
19
+ /**
20
+ * Generate the HTML shell for a page, with appropriate script tags
21
+ * depending on dev vs production mode.
22
+ */
23
+ export declare function getHtmlScripts(mode: 'development' | 'production', config?: Partial<MoriaConfig>): string;
24
+ //# sourceMappingURL=vite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;GAGG;AACH,wBAAsB,uBAAuB,CACzC,MAAM,EAAE,eAAe,EACvB,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAClC,OAAO,CAAC,aAAa,CAAC,CA4BxB;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACvC,MAAM,EAAE,eAAe,EACvB,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAClC,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,EAAE,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,MAAM,CAW5G"}
package/dist/vite.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Vite integration for MoriaJS.
3
+ *
4
+ * Provides Vite dev server in middleware mode (development)
5
+ * and static file serving for production builds.
6
+ */
7
+ /**
8
+ * Attach Vite dev server to Fastify in middleware mode.
9
+ * This enables HMR and on-the-fly module transformation.
10
+ */
11
+ export async function createViteDevMiddleware(server, config = {}) {
12
+ const { createServer: createViteServer } = await import('vite');
13
+ const middie = (await import('@fastify/middie')).default;
14
+ // Register Express-style middleware support
15
+ await server.register(middie);
16
+ const vite = await createViteServer({
17
+ root: config.rootDir ?? process.cwd(),
18
+ configFile: config.vite?.configFile,
19
+ server: {
20
+ middlewareMode: true,
21
+ hmr: true,
22
+ },
23
+ appType: 'custom',
24
+ });
25
+ // Use Vite's connect middleware stack
26
+ server.use(vite.middlewares);
27
+ server.log.info('Vite dev server attached (HMR enabled)');
28
+ // Ensure Vite is closed when Fastify shuts down
29
+ server.addHook('onClose', async () => {
30
+ await vite.close();
31
+ });
32
+ return vite;
33
+ }
34
+ /**
35
+ * Serve production-built client assets via @fastify/static.
36
+ */
37
+ export async function serveProductionAssets(server, config = {}) {
38
+ const path = await import('node:path');
39
+ const fastifyStatic = (await import('@fastify/static')).default;
40
+ const distDir = path.resolve(config.rootDir ?? process.cwd(), 'dist', 'client');
41
+ await server.register(fastifyStatic, {
42
+ root: distDir,
43
+ prefix: '/assets/',
44
+ decorateReply: false,
45
+ });
46
+ server.log.info(`Serving static assets from ${distDir}`);
47
+ }
48
+ /**
49
+ * Generate the HTML shell for a page, with appropriate script tags
50
+ * depending on dev vs production mode.
51
+ */
52
+ export function getHtmlScripts(mode, config = {}) {
53
+ if (mode === 'development') {
54
+ const clientEntry = config.vite?.clientEntry ?? '/src/entry-client.ts';
55
+ return [
56
+ `<script type="module" src="/@vite/client"></script>`,
57
+ `<script type="module" src="${clientEntry}"></script>`,
58
+ ].join('\n ');
59
+ }
60
+ // Production: reference the built bundle
61
+ return `<script type="module" src="/assets/entry-client.js"></script>`;
62
+ }
63
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CACzC,MAAuB,EACvB,SAA+B,EAAE;IAEjC,MAAM,EAAE,YAAY,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC;IAEzD,4CAA4C;IAC5C,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE9B,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC;QAChC,IAAI,EAAE,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE;QACrC,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,UAAU;QACnC,MAAM,EAAE;YACJ,cAAc,EAAE,IAAI;YACpB,GAAG,EAAE,IAAI;SACZ;QACD,OAAO,EAAE,QAAQ;KACpB,CAAC,CAAC;IAEH,sCAAsC;IACtC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAE7B,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;IAE1D,gDAAgD;IAChD,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;QACjC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACvC,MAAuB,EACvB,SAA+B,EAAE;IAEjC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;IACvC,MAAM,aAAa,GAAG,CAAC,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC;IAEhE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEhF,MAAM,MAAM,CAAC,QAAQ,CAAC,aAAa,EAAE;QACjC,IAAI,EAAE,OAAO;QACb,MAAM,EAAE,UAAU;QAClB,aAAa,EAAE,KAAK;KACvB,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,8BAA8B,OAAO,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAkC,EAAE,SAA+B,EAAE;IAChG,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;QACzB,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,EAAE,WAAW,IAAI,sBAAsB,CAAC;QACvE,OAAO;YACH,qDAAqD;YACrD,8BAA8B,WAAW,aAAa;SACzD,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC;IAED,yCAAyC;IACzC,OAAO,+DAA+D,CAAC;AAC3E,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moriajs/core",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "MoriaJS framework core — Fastify server, Vite integration, and routing",
6
6
  "main": "./dist/index.js",
@@ -18,13 +18,19 @@
18
18
  "@fastify/compress": "^8.0.0",
19
19
  "@fastify/static": "^9.0.0",
20
20
  "@fastify/helmet": "^13.0.0",
21
- "pino-pretty": "^13.0.0"
21
+ "@fastify/middie": "^9.0.0",
22
+ "pino-pretty": "^13.0.0",
23
+ "vite": "^6.0.0",
24
+ "glob": "^11.0.0"
22
25
  },
23
26
  "devDependencies": {
24
27
  "typescript": "^5.7.0",
25
28
  "rimraf": "^6.0.0",
26
29
  "@types/node": "^22.0.0"
27
30
  },
31
+ "peerDependencies": {
32
+ "@moriajs/renderer": "0.2.0"
33
+ },
28
34
  "license": "MIT",
29
35
  "author": "Guntur-D <guntur.d.npm@gmail.com>",
30
36
  "repository": {
package/src/app.ts CHANGED
@@ -3,8 +3,13 @@ import cors from '@fastify/cors';
3
3
  import cookie from '@fastify/cookie';
4
4
  import compress from '@fastify/compress';
5
5
  import helmet from '@fastify/helmet';
6
+ import path from 'node:path';
7
+ import fs from 'node:fs';
6
8
  import { type MoriaConfig } from './config.js';
7
9
  import { type MoriaPlugin } from './plugins.js';
10
+ import { createViteDevMiddleware, serveProductionAssets } from './vite.js';
11
+ import { registerRoutes } from './router.js';
12
+ import type { ViteDevServer } from 'vite';
8
13
 
9
14
  /**
10
15
  * Options for creating a MoriaJS application.
@@ -23,6 +28,8 @@ export interface MoriaAppOptions {
23
28
  export interface MoriaApp {
24
29
  /** The underlying Fastify instance */
25
30
  server: FastifyInstance;
31
+ /** The Vite dev server (only in development mode) */
32
+ vite?: ViteDevServer;
26
33
  /** Register a MoriaJS plugin */
27
34
  use: (plugin: MoriaPlugin) => Promise<void>;
28
35
  /** Start the server */
@@ -38,12 +45,14 @@ export interface MoriaApp {
38
45
  * ```ts
39
46
  * import { createApp } from '@moriajs/core';
40
47
  *
41
- * const app = await createApp();
48
+ * const app = await createApp({ config: { mode: 'development' } });
42
49
  * await app.listen({ port: 3000 });
43
50
  * ```
44
51
  */
45
52
  export async function createApp(options: MoriaAppOptions = {}): Promise<MoriaApp> {
46
53
  const config = options.config ?? {};
54
+ const mode = config.mode ?? (process.env.NODE_ENV === 'production' ? 'production' : 'development');
55
+ const rootDir = config.rootDir ?? process.cwd();
47
56
  const port = config.server?.port ?? 3000;
48
57
  const host = config.server?.host ?? '0.0.0.0';
49
58
 
@@ -52,7 +61,7 @@ export async function createApp(options: MoriaAppOptions = {}): Promise<MoriaApp
52
61
  logger: {
53
62
  level: config.server?.logLevel ?? 'info',
54
63
  transport:
55
- process.env.NODE_ENV !== 'production'
64
+ mode !== 'production'
56
65
  ? { target: 'pino-pretty', options: { colorize: true } }
57
66
  : undefined,
58
67
  },
@@ -69,17 +78,33 @@ export async function createApp(options: MoriaAppOptions = {}): Promise<MoriaApp
69
78
  await server.register(compress);
70
79
  await server.register(helmet, {
71
80
  // Relax CSP in development for Vite HMR
72
- contentSecurityPolicy: process.env.NODE_ENV === 'production',
81
+ contentSecurityPolicy: mode === 'production',
73
82
  });
74
83
 
75
84
  // Health check route
76
85
  server.get('/health', async () => ({ status: 'ok', timestamp: new Date().toISOString() }));
77
86
 
87
+ // ─── Vite Integration ────────────────────────────────────
88
+ let vite: ViteDevServer | undefined;
89
+
90
+ if (mode === 'development') {
91
+ vite = await createViteDevMiddleware(server, { ...config, rootDir });
92
+ } else {
93
+ await serveProductionAssets(server, { ...config, rootDir });
94
+ }
95
+
96
+ // ─── File-Based Routing ──────────────────────────────────
97
+ const routesDir = path.resolve(rootDir, config.routes?.dir ?? 'src/routes');
98
+ if (fs.existsSync(routesDir)) {
99
+ await registerRoutes(server, routesDir, { mode, config });
100
+ }
101
+
78
102
  // Plugin registry
79
103
  const plugins: MoriaPlugin[] = [];
80
104
 
81
105
  const app: MoriaApp = {
82
106
  server,
107
+ vite,
83
108
 
84
109
  async use(plugin: MoriaPlugin) {
85
110
  plugins.push(plugin);
package/src/config.ts CHANGED
@@ -3,6 +3,12 @@
3
3
  * Used in `moria.config.ts` files in user projects.
4
4
  */
5
5
  export interface MoriaConfig {
6
+ /** Application mode */
7
+ mode?: 'development' | 'production';
8
+
9
+ /** Project root directory (auto-detected if not set) */
10
+ rootDir?: string;
11
+
6
12
  /** Server configuration */
7
13
  server?: {
8
14
  /** Port to listen on (default: 3000) */
@@ -44,6 +50,14 @@ export interface MoriaConfig {
44
50
  vite?: {
45
51
  /** Path to Vite config file */
46
52
  configFile?: string;
53
+ /** Client entry point (default: '/src/entry-client.ts') */
54
+ clientEntry?: string;
55
+ };
56
+
57
+ /** File-based routing configuration */
58
+ routes?: {
59
+ /** Routes directory relative to rootDir (default: 'src/routes') */
60
+ dir?: string;
47
61
  };
48
62
  }
49
63
 
package/src/index.ts CHANGED
@@ -2,13 +2,16 @@
2
2
  * @moriajs/core
3
3
  *
4
4
  * Framework core: Fastify server factory with sensible defaults,
5
- * plugin registration, and configuration system.
5
+ * plugin registration, Vite integration, and file-based routing.
6
6
  */
7
7
 
8
8
  export { createApp } from './app.js';
9
9
  export { defineConfig } from './config.js';
10
10
  export { defineMoriaPlugin } from './plugins.js';
11
+ export { createViteDevMiddleware, serveProductionAssets, getHtmlScripts } from './vite.js';
12
+ export { scanRoutes, registerRoutes, filePathToUrlPath } from './router.js';
11
13
 
12
14
  export type { MoriaApp, MoriaAppOptions } from './app.js';
13
15
  export type { MoriaConfig } from './config.js';
14
16
  export type { MoriaPlugin, MoriaPluginContext } from './plugins.js';
17
+ export type { RouteEntry, RouteHandler } from './router.js';
package/src/router.ts ADDED
@@ -0,0 +1,278 @@
1
+ /**
2
+ * File-based routing for MoriaJS.
3
+ *
4
+ * Scans `src/routes/` for route files and auto-registers them with Fastify.
5
+ *
6
+ * Convention:
7
+ * src/routes/api/hello.ts → GET /api/hello (API handler)
8
+ * src/routes/api/users/[id].ts → GET /api/users/:id (API handler)
9
+ * src/routes/pages/index.ts → GET / (SSR page)
10
+ * src/routes/pages/about.ts → GET /about (SSR page)
11
+ *
12
+ * API routes export named HTTP method functions:
13
+ * export function GET(request, reply) { ... }
14
+ *
15
+ * Page routes export a Mithril component + optional data loader:
16
+ * export default { view() { return m('h1', 'Hello') } }
17
+ * export async function getServerData(request) { return { user: ... } }
18
+ */
19
+
20
+ import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
21
+ import { glob } from 'glob';
22
+ import path from 'node:path';
23
+ import { pathToFileURL } from 'node:url';
24
+ import type { MoriaConfig } from './config.js';
25
+
26
+ /** Supported HTTP methods in route files. */
27
+ const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] as const;
28
+ type HttpMethod = (typeof HTTP_METHODS)[number];
29
+
30
+ /** Handler function exported from a route file. */
31
+ export type RouteHandler = (request: FastifyRequest, reply: FastifyReply) => unknown | Promise<unknown>;
32
+
33
+ /** Data loader for page routes — runs on server before render. */
34
+ export type GetServerData = (request: FastifyRequest) => unknown | Promise<unknown>;
35
+
36
+ /** Discovered route entry. */
37
+ export interface RouteEntry {
38
+ /** File path relative to routes dir */
39
+ filePath: string;
40
+ /** URL path pattern (e.g., /api/users/:id) */
41
+ urlPath: string;
42
+ /** Route type: 'api' or 'page' */
43
+ type: 'api' | 'page';
44
+ /** HTTP method → handler map (API routes) */
45
+ methods: Partial<Record<Lowercase<HttpMethod>, RouteHandler>>;
46
+ /** Mithril component (page routes only) */
47
+ component?: unknown;
48
+ /** Server data loader (page routes only) */
49
+ getServerData?: GetServerData;
50
+ }
51
+
52
+ /**
53
+ * Options for route registration.
54
+ */
55
+ export interface RegisterRoutesOptions {
56
+ /** Application mode */
57
+ mode?: 'development' | 'production';
58
+ /** MoriaJS config for renderer options */
59
+ config?: Partial<MoriaConfig>;
60
+ }
61
+
62
+ /**
63
+ * Convert a file path to a URL path.
64
+ *
65
+ * - Strips file extension
66
+ * - Converts `[param]` → `:param`
67
+ * - Converts `[...slug]` → `*`
68
+ * - Converts `index` → `/`
69
+ *
70
+ * @example
71
+ * filePathToUrlPath('api/users/[id].ts') → '/api/users/:id'
72
+ * filePathToUrlPath('pages/index.ts') → '/'
73
+ * filePathToUrlPath('pages/about.ts') → '/about'
74
+ */
75
+ export function filePathToUrlPath(filePath: string): string {
76
+ // Remove extension
77
+ let route = filePath.replace(/\.(ts|js|mts|mjs)$/, '');
78
+
79
+ // Normalize separators
80
+ route = route.replace(/\\/g, '/');
81
+
82
+ // Convert [param] → :param
83
+ route = route.replace(/\[([^\].]+)\]/g, ':$1');
84
+
85
+ // Convert [...slug] → *
86
+ route = route.replace(/\[\.\.\.([^\]]+)\]/g, '*');
87
+
88
+ // Handle pages prefix — strip "pages" and make root-relative
89
+ if (route.startsWith('pages/')) {
90
+ route = route.slice(5); // remove "pages"
91
+ }
92
+ // Handle api prefix — keep "api"
93
+ // (no transformation needed)
94
+
95
+ // Handle index files → parent path
96
+ route = route.replace(/\/index$/, '');
97
+
98
+ // Ensure leading slash
99
+ if (!route.startsWith('/')) {
100
+ route = '/' + route;
101
+ }
102
+
103
+ // Root case
104
+ if (route === '') {
105
+ route = '/';
106
+ }
107
+
108
+ return route;
109
+ }
110
+
111
+ /**
112
+ * Scan a directory for route files and return discovered routes.
113
+ *
114
+ * @param routesDir - Absolute path to the routes directory (e.g., `<project>/src/routes`)
115
+ */
116
+ export async function scanRoutes(routesDir: string): Promise<RouteEntry[]> {
117
+ const pattern = '**/*.{ts,js,mts,mjs}';
118
+ const files = await glob(pattern, {
119
+ cwd: routesDir,
120
+ posix: true,
121
+ ignore: ['**/_*', '**/*.d.ts', '**/*.test.*', '**/*.spec.*'],
122
+ });
123
+
124
+ const routes: RouteEntry[] = [];
125
+
126
+ for (const file of files) {
127
+ const urlPath = filePathToUrlPath(file);
128
+ const type: 'api' | 'page' = file.startsWith('api/') ? 'api' : 'page';
129
+
130
+ const absolutePath = path.resolve(routesDir, file);
131
+ const fileUrl = pathToFileURL(absolutePath).href;
132
+
133
+ // Dynamically import the route module
134
+ let mod: Record<string, unknown>;
135
+ try {
136
+ mod = await import(fileUrl);
137
+ } catch (err) {
138
+ console.warn(`[moria] Failed to load route: ${file}`, err);
139
+ continue;
140
+ }
141
+
142
+ if (type === 'page') {
143
+ // ─── Page route: expects default Mithril component ───────
144
+ const component = mod.default;
145
+ const getServerData = typeof mod.getServerData === 'function'
146
+ ? mod.getServerData as GetServerData
147
+ : undefined;
148
+
149
+ // Page routes can also export raw HTTP handlers (backward compat)
150
+ if (component && typeof component === 'object' && 'view' in component) {
151
+ routes.push({
152
+ filePath: file,
153
+ urlPath,
154
+ type: 'page',
155
+ methods: {},
156
+ component,
157
+ getServerData,
158
+ });
159
+ continue;
160
+ }
161
+
162
+ // Fallback: if default export is a function, treat as API-style handler
163
+ if (typeof component === 'function') {
164
+ routes.push({
165
+ filePath: file,
166
+ urlPath,
167
+ type: 'page',
168
+ methods: { get: component as RouteHandler },
169
+ });
170
+ continue;
171
+ }
172
+
173
+ // Also check for named HTTP method exports
174
+ const methods: Partial<Record<Lowercase<HttpMethod>, RouteHandler>> = {};
175
+ for (const method of HTTP_METHODS) {
176
+ const handler = mod[method] ?? mod[method.toLowerCase()];
177
+ if (typeof handler === 'function') {
178
+ methods[method.toLowerCase() as Lowercase<HttpMethod>] = handler as RouteHandler;
179
+ }
180
+ }
181
+ if (Object.keys(methods).length > 0) {
182
+ routes.push({ filePath: file, urlPath, type: 'page', methods });
183
+ continue;
184
+ }
185
+
186
+ console.warn(`[moria] Page route has no component or handlers: ${file}`);
187
+ } else {
188
+ // ─── API route: expects named HTTP method exports ────────
189
+ const methods: Partial<Record<Lowercase<HttpMethod>, RouteHandler>> = {};
190
+ for (const method of HTTP_METHODS) {
191
+ const handler = mod[method] ?? mod[method.toLowerCase()];
192
+ if (typeof handler === 'function') {
193
+ methods[method.toLowerCase() as Lowercase<HttpMethod>] = handler as RouteHandler;
194
+ }
195
+ }
196
+
197
+ // Also support default export as GET handler
198
+ if (typeof mod.default === 'function' && !methods.get) {
199
+ methods.get = mod.default as RouteHandler;
200
+ }
201
+
202
+ if (Object.keys(methods).length === 0) {
203
+ console.warn(`[moria] Route file has no handlers: ${file}`);
204
+ continue;
205
+ }
206
+
207
+ routes.push({ filePath: file, urlPath, type: 'api', methods });
208
+ }
209
+ }
210
+
211
+ return routes;
212
+ }
213
+
214
+ /**
215
+ * Register discovered routes with a Fastify server.
216
+ *
217
+ * Page routes with Mithril components are auto-wrapped with SSR rendering.
218
+ * API routes are registered directly as Fastify handlers.
219
+ */
220
+ export async function registerRoutes(
221
+ server: FastifyInstance,
222
+ routesDir: string,
223
+ options: RegisterRoutesOptions = {}
224
+ ): Promise<RouteEntry[]> {
225
+ const routes = await scanRoutes(routesDir);
226
+ const mode = options.mode ?? 'development';
227
+ const config = options.config ?? {};
228
+
229
+ for (const route of routes) {
230
+ // ─── Page route with Mithril component → SSR handler ─────
231
+ if (route.component) {
232
+ const component = route.component;
233
+ const getServerData = route.getServerData;
234
+ const clientEntry = config.vite?.clientEntry ?? '/src/entry-client.ts';
235
+
236
+ server.route({
237
+ method: 'GET',
238
+ url: route.urlPath,
239
+ handler: async (request: FastifyRequest, reply: FastifyReply) => {
240
+ // Load server data if available
241
+ let initialData: Record<string, unknown> | undefined;
242
+ if (getServerData) {
243
+ initialData = (await getServerData(request)) as Record<string, unknown>;
244
+ }
245
+
246
+ // Dynamic import of renderer (avoids circular deps)
247
+ const { renderToString } = await import('@moriajs/renderer');
248
+
249
+ const html = await renderToString(component, {
250
+ title: (component as { title?: string }).title ?? 'MoriaJS App',
251
+ initialData,
252
+ mode,
253
+ clientEntry,
254
+ });
255
+
256
+ reply.type('text/html');
257
+ return html;
258
+ },
259
+ });
260
+
261
+ server.log.info(`Page: GET ${route.urlPath} → ${route.filePath} (SSR)`);
262
+ continue;
263
+ }
264
+
265
+ // ─── API / raw handler routes ────────────────────────────
266
+ for (const [method, handler] of Object.entries(route.methods)) {
267
+ server.route({
268
+ method: method.toUpperCase() as Uppercase<string>,
269
+ url: route.urlPath,
270
+ handler: handler as RouteHandler,
271
+ });
272
+ server.log.info(`Route: ${method.toUpperCase()} ${route.urlPath} → ${route.filePath}`);
273
+ }
274
+ }
275
+
276
+ server.log.info(`Registered ${routes.length} file-based route(s)`);
277
+ return routes;
278
+ }
package/src/vite.ts ADDED
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Vite integration for MoriaJS.
3
+ *
4
+ * Provides Vite dev server in middleware mode (development)
5
+ * and static file serving for production builds.
6
+ */
7
+
8
+ import type { FastifyInstance } from 'fastify';
9
+ import type { ViteDevServer } from 'vite';
10
+ import { type MoriaConfig } from './config.js';
11
+
12
+ /**
13
+ * Attach Vite dev server to Fastify in middleware mode.
14
+ * This enables HMR and on-the-fly module transformation.
15
+ */
16
+ export async function createViteDevMiddleware(
17
+ server: FastifyInstance,
18
+ config: Partial<MoriaConfig> = {}
19
+ ): Promise<ViteDevServer> {
20
+ const { createServer: createViteServer } = await import('vite');
21
+ const middie = (await import('@fastify/middie')).default;
22
+
23
+ // Register Express-style middleware support
24
+ await server.register(middie);
25
+
26
+ const vite = await createViteServer({
27
+ root: config.rootDir ?? process.cwd(),
28
+ configFile: config.vite?.configFile,
29
+ server: {
30
+ middlewareMode: true,
31
+ hmr: true,
32
+ },
33
+ appType: 'custom',
34
+ });
35
+
36
+ // Use Vite's connect middleware stack
37
+ server.use(vite.middlewares);
38
+
39
+ server.log.info('Vite dev server attached (HMR enabled)');
40
+
41
+ // Ensure Vite is closed when Fastify shuts down
42
+ server.addHook('onClose', async () => {
43
+ await vite.close();
44
+ });
45
+
46
+ return vite;
47
+ }
48
+
49
+ /**
50
+ * Serve production-built client assets via @fastify/static.
51
+ */
52
+ export async function serveProductionAssets(
53
+ server: FastifyInstance,
54
+ config: Partial<MoriaConfig> = {}
55
+ ): Promise<void> {
56
+ const path = await import('node:path');
57
+ const fastifyStatic = (await import('@fastify/static')).default;
58
+
59
+ const distDir = path.resolve(config.rootDir ?? process.cwd(), 'dist', 'client');
60
+
61
+ await server.register(fastifyStatic, {
62
+ root: distDir,
63
+ prefix: '/assets/',
64
+ decorateReply: false,
65
+ });
66
+
67
+ server.log.info(`Serving static assets from ${distDir}`);
68
+ }
69
+
70
+ /**
71
+ * Generate the HTML shell for a page, with appropriate script tags
72
+ * depending on dev vs production mode.
73
+ */
74
+ export function getHtmlScripts(mode: 'development' | 'production', config: Partial<MoriaConfig> = {}): string {
75
+ if (mode === 'development') {
76
+ const clientEntry = config.vite?.clientEntry ?? '/src/entry-client.ts';
77
+ return [
78
+ `<script type="module" src="/@vite/client"></script>`,
79
+ `<script type="module" src="${clientEntry}"></script>`,
80
+ ].join('\n ');
81
+ }
82
+
83
+ // Production: reference the built bundle
84
+ return `<script type="module" src="/assets/entry-client.js"></script>`;
85
+ }