noxt-server 0.1.15 → 0.1.16

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
@@ -1,118 +1,5 @@
1
- # noxt-js
1
+ # noxt-server
2
2
 
3
- A zero-config JSX web server powered by [Express](https://expressjs.com/) and [noxt-js-middleware](https://npmjs.com/package/noxt-js-middleware).
4
- Run it with `npx noxt-js` and drop `.jsx` files into your `views/` folder — routes are created automatically.
3
+ Server stack for [noxt-js-middleware](https://npmjs.com/package/noxt-js-middleware)
5
4
 
6
- No React required. No heavy framework. Just JSX + Express.
7
-
8
- ---
9
-
10
- ## Installation
11
-
12
- ```sh
13
- npm install noxt-js
14
- ```
15
-
16
- or run it directly without installing:
17
-
18
- ```sh
19
- npx noxt-js
20
- ```
21
-
22
- ---
23
-
24
- ## Quick Start
25
-
26
- ```sh
27
- npx noxt-js
28
- ```
29
-
30
- By default, this will:
31
-
32
- - Serve pages from `./views/`
33
- - Start an HTTP server on port `3000`
34
- - Look for configuration in `noxt.config.yaml` (optional)
35
-
36
- ---
37
-
38
- ## Configuration
39
-
40
- `noxt-js` reads options from `noxt.config.yaml` in your project root, or from CLI flags.
41
- CLI flags override config file values.
42
-
43
- Example `noxt.config.yaml`:
44
-
45
- ```yaml
46
- port: 4000
47
- host: localhost
48
- views: views
49
- logLevel: info
50
- ssl: false
51
- ```
52
-
53
- Run with CLI overrides:
54
-
55
- ```sh
56
- npx noxt-js --port 8080 --views src/pages
57
- ```
58
-
59
- ### Available options
60
-
61
- - **`port`**: HTTP port number (default: `3000`)
62
- - **`host`**: Hostname or IP (default: `0.0.0.0`)
63
- - **`views`**: Directory containing `.jsx` files (default: `views`)
64
- - **`logLevel`**: One of `error`, `warn`, `info`, `debug`
65
- - **`ssl`**:
66
- - `false` (disable SSL)
67
- - or object with `cert` and `key` paths for HTTPS
68
-
69
- ---
70
-
71
- ## Context
72
-
73
- You can provide shared helpers/utilities to all components via a `context.js` file (or any path you specify in config). For example:
74
-
75
- ```js
76
- // context.js
77
- export async function fetchUser(id) {
78
- return db.users.findById(id);
79
- }
80
- ```
81
-
82
- Then in a page:
83
-
84
- ```jsx
85
- export const route = '/user/:id';
86
-
87
- export default async function UserPage({ id }, { fetchUser }) {
88
- const user = await fetchUser(id);
89
- return <h1>{user.name}</h1>;
90
- }
91
- ```
92
-
93
- ---
94
-
95
- ## Pages & Routing
96
-
97
- - Any `.jsx` file in `views/` is loaded as a component.
98
- - If it exports `route`, it becomes a page at that route.
99
- - Props come from route params, query string, and optional `params` export.
100
-
101
- Example:
102
-
103
- ```jsx
104
- export const route = '/hello/:name';
105
-
106
- export default function HelloPage({ name }) {
107
- return <h1>Hello, {name}!</h1>;
108
- }
109
- ```
110
-
111
- ---
112
-
113
-
114
- ---
115
-
116
- ## License
117
-
118
- LGPL-3.0-or-later
5
+ Run with [noxt-cli](https://npmjs.com/package/noxt-cli)
package/noxt-server.js CHANGED
@@ -54,7 +54,7 @@ export async function startServer({ config, recipe, import: _import}) {
54
54
  '| Start', Number((afterStart - afterConfig) / 1_000_000n), 'ms',
55
55
  );
56
56
 
57
- mlmInstance.repl();
57
+ mlm.DEV && mlmInstance.repl();
58
58
  } catch (e) {
59
59
  console.log(await mlmInstance.analyze(recipe ?? 'noxt-dev'));
60
60
  console.error(e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noxt-server",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Server for noxt-js-middleware with CLI and config support",
5
5
  "type": "module",
6
6
  "main": "noxt-server.js",
@@ -23,8 +23,8 @@
23
23
  "author": "Zoran Obradović [https://github.com/zocky]",
24
24
  "license": "GPL-3.0-or-later",
25
25
  "dependencies": {
26
- "noxt-js-middleware": "^1.0.7",
27
- "mlm-core": "^1.0.7",
26
+ "noxt-js-middleware": "^1.0.9",
27
+ "mlm-core": "^1.0.8",
28
28
  "express": "^5.1.0",
29
29
  "cookie-parser": "^1.4.6",
30
30
  "js-yaml": "^4.1.0"
@@ -33,10 +33,6 @@
33
33
  "compression": "^1.8.1",
34
34
  "sharp": "^0.34.5"
35
35
  },
36
- "devDependencies": {
37
- "noxt-js-middleware": "../noxt-js-middleware",
38
- "mlm-core": "../mlm-core"
39
- },
40
36
  "engines": {
41
37
  "node": ">=18"
42
38
  }
package/units/bundler.js CHANGED
@@ -22,7 +22,17 @@ export default mlm => {
22
22
  'services.bundler': () => ({ styles, scripts }),
23
23
  'registerComponent.bundler': async ({ name, component, module }) => {
24
24
  styles[component.name] = module.style ? `/* ${name} */ \n ${module.style}\n` : '';
25
- scripts[component.name] = module.script ? `/* ${name} */ \n ${module.script}\n` : '';
25
+
26
+ if (!module.script) return;
27
+
28
+ if (typeof module.script == 'string') {
29
+ scripts[component.name] = `/* ${name} */ \n (()=>{\n${module.script}\n})();\n\n`;
30
+ return
31
+ }
32
+ if (typeof module.script == 'function') {
33
+ scripts[component.name] = `/* ${name} */ \n (${module.script.toString()})(window);\n\n`;
34
+ return
35
+ }
26
36
  },
27
37
  'middleware.bundler': () => {
28
38
  const router = mlm.services.express.Router();
@@ -0,0 +1,30 @@
1
+ export const info = {
2
+ name: 'bust-cache',
3
+ version: '1.0.0',
4
+ description: 'Bust cache on GET requests',
5
+ requires: ['express'],
6
+ }
7
+
8
+ export default mlm => ({
9
+ 'hook.bustCache': async (fn, req) => {
10
+ req.bustCache ||= fn(req);
11
+ },
12
+ 'prod.middleware.bustCache': async () => {
13
+ return (req, res, next) => {
14
+ if (req.method === 'GET') {
15
+ mlm.hook.bustCache(req);
16
+ }
17
+ next();
18
+ };
19
+ },
20
+ 'dev.middleware.bustCache': () => {
21
+ return (req, res, next) => {
22
+ if (req.method === 'GET') {
23
+ if (req.headers['cache-control']?.includes('no-cache')) {
24
+ req.bustCache = true;
25
+ }
26
+ }
27
+ next();
28
+ };
29
+ },
30
+ });
@@ -4,10 +4,7 @@ export const info = {
4
4
  version: '1.0.0',
5
5
  description: 'Response compression',
6
6
  requires: ['express'],
7
- npm: {
8
- 'compression': '^1.8.0'
9
- },
10
-
7
+ packages: ['compression'],
11
8
  }
12
9
 
13
10
  export default mlm => ({
@@ -27,7 +24,7 @@ export default mlm => ({
27
24
  'middleware.compression': async () => {
28
25
  const { enabled, level, threshold } = mlm.config.compression;
29
26
  if (!enabled) return null;
30
- const compression = (await mlm.import('compression')).default;
27
+ const compression = mlm.packages.compression.default;
31
28
  return compression({ level, threshold });
32
29
  },
33
30
  });
package/units/env.js CHANGED
@@ -9,6 +9,7 @@ export default mlm => ({
9
9
  'define.PROD': () => process.env.NODE_ENV === 'production',
10
10
  'onBeforeLoad': () => {
11
11
  process.env.NODE_ENV ||= 'development'
12
- }
12
+ },
13
+ 'inject.dev': (confs, unit) => mlm.DEV ? confs : null,
14
+ 'inject.prod': (confs, unit) => !mlm.DEV ? confs : null,
13
15
  })
14
-
package/units/express.js CHANGED
@@ -2,17 +2,14 @@ export const info = {
2
2
  name: 'express',
3
3
  description: 'Sets up an Express server',
4
4
  requires: ['plugin'],
5
- npm: {
6
- 'express': '^5.0.0',
7
- 'cookie-parser': '^1.4.6'
8
- },
5
+ packages: ['express'],
9
6
  }
10
7
 
11
8
  export default async mlm => {
12
9
  let app;
13
10
  const middlewareNames = new Set();
14
11
  const middlewares = [];
15
- const { default: express } = await mlm.import('express');
12
+ const { default: express } = mlm.packages.express;
16
13
  return {
17
14
  'services.express': () => new class {
18
15
  get app () {
package/units/hooks.js CHANGED
@@ -34,7 +34,7 @@ export default mlm => {
34
34
  'collect.on': {
35
35
  target: hook_handlers,
36
36
  is: 'function',
37
- mode: 'array'
37
+ mode: 'arrays'
38
38
  }
39
39
  })
40
40
  }
@@ -3,9 +3,7 @@ export const info = {
3
3
  description: 'Image resizing and caching service',
4
4
  requires: ['express'],
5
5
  provides: ['#image-resizer'],
6
- npm: {
7
- 'sharp': '^0.33.0',
8
- },
6
+ packages: ['sharp','express']
9
7
  }
10
8
 
11
9
  import crypto from 'crypto';
@@ -35,7 +33,7 @@ function getShardedPath(hash, ext, depth, segmentLength) {
35
33
  }
36
34
 
37
35
  export default async mlm => {
38
- const sharp = (await mlm.import('sharp')).default;
36
+ const {default:sharp} = mlm.packages.sharp;
39
37
 
40
38
  async function getMetadata(hash) {
41
39
  const shardPath = getShardedPath(hash, 'json', mlm.config.imageResizer.shardDepth, mlm.config.imageResizer.shardSegmentLength);
@@ -82,26 +80,13 @@ export default async mlm => {
82
80
  const resizeOptions = {
83
81
  width: +options.width || null,
84
82
  height: +options.height || null,
85
- fit: options.fit === 'crop' ? 'cover' : options.fit,
86
- withoutEnlargement: options.fit === 'crop'
83
+ fit: options.fit || 'inside',
87
84
  };
88
85
 
89
- // Add background color if specified (for contain fit letterboxing)
90
86
  if (options.background) {
91
87
  resizeOptions.background = options.background;
92
88
  }
93
-
94
89
  image = image.resize(resizeOptions);
95
-
96
- // If crop fit, extract exact dimensions
97
- if (options.fit === 'crop' && options.width && options.height) {
98
- image = image.extract({
99
- left: 0,
100
- top: 0,
101
- width: +options.width,
102
- height: +options.height
103
- });
104
- }
105
90
  }
106
91
 
107
92
  // Set format and quality
@@ -206,7 +191,7 @@ export default async mlm => {
206
191
  }),
207
192
 
208
193
  'middleware.imageResizerServe': async () => {
209
- const { default: express } = await mlm.import('express');
194
+ const { default: express } = mlm.packages.express;
210
195
  const router = express.Router();
211
196
 
212
197
  // Static middleware runs FIRST, mounted at the configured route
@@ -216,12 +201,14 @@ export default async mlm => {
216
201
  setHeaders: res => res.logGroup = 'static',
217
202
  }));
218
203
 
219
- return router;
204
+ return (req,res,next) => {
205
+ if (req.bustCache) return next();
206
+ router(req, res, next);
207
+ }
220
208
  },
221
209
 
222
210
  'middleware.imageResizerProcess': async () => {
223
- const { default: express } = await mlm.import('express');
224
- const router = express.Router();
211
+ const router = mlm.packages.express.Router();
225
212
 
226
213
  // This runs SECOND, only if static didn't find the file
227
214
  router.get(`${mlm.config.imageResizer.route}/*path`, async (req, res, next) => {
@@ -238,8 +225,6 @@ export default async mlm => {
238
225
  return res.status(404).send('Image not found');
239
226
  }
240
227
 
241
- const noCache = req.headers.pragma === 'no-cache' ||
242
- req.headers['cache-control']?.includes('no-cache');
243
228
 
244
229
  // Process and serve the image
245
230
  try {
package/units/logger.js CHANGED
@@ -55,7 +55,7 @@ export default mlm => ({
55
55
  (res._realBodySize / 1024).toFixed(0).padStart(4, ' ') + 'K',
56
56
  res.statusCode,
57
57
  req.method + ' ' +
58
- req.originalUrl || req.url,
58
+ (req.originalUrl || req.url),
59
59
  //ua
60
60
  ]
61
61
  mlm.log(parts.join(' | '));
package/units/noxt-dev.js CHANGED
@@ -4,12 +4,12 @@ export const info = {
4
4
  requires: [
5
5
  'express',
6
6
  'logger',
7
+ 'bundler',
7
8
  'reload',
8
9
  'compression',
9
- 'static-dev',
10
+ 'static',
10
11
  'fetch-cache-fs',
11
- 'bundler',
12
- 'noxt-router-dev',
12
+ 'noxt-router',
13
13
  'fetch',
14
14
  ],
15
15
  }
@@ -6,6 +6,7 @@ export const info = {
6
6
 
7
7
 
8
8
  export default mlm => {
9
+ const requestApi = {};
9
10
  const componentErrorHooks = {};
10
11
  const requestContextHooks = {};
11
12
  const serverContextHooks = {};
@@ -21,6 +22,15 @@ export default mlm => {
21
22
  is: 'function',
22
23
  mode: 'object'
23
24
  },
25
+ 'collect.api': {
26
+ target: requestApi,
27
+ is: 'function',
28
+ mode: 'object',
29
+ },
30
+ 'requestContext.api': ({ ctx }) => new Proxy({}, {
31
+ ownKeys: () => Object.keys(requestApi),
32
+ get: (target, key) => (target[key] ??= requestApi[key]?.({ctx}))
33
+ }),
24
34
  'collect.componentExports': {
25
35
  target: componentExportsHooks,
26
36
  is: 'object',
@@ -106,8 +116,8 @@ export default mlm => {
106
116
  },
107
117
  'registerPage.Link'({ component, module }) {
108
118
  const As = module.Link ?? 'a';
109
- component.Link = ({ text, children, attrs, ...props }, ctx) => {
110
- const href = component.getRoutePath(props, ctx);
119
+ component.Link = ({ text, children, params={}, ...attrs }, ctx) => {
120
+ const href = component.getRoutePath(params, ctx);
111
121
  return { type: As, props: { ...attrs, href, children: text ?? children } };
112
122
  }
113
123
  },
@@ -3,9 +3,9 @@ export const info = {
3
3
  description: 'Sets up a Noxt Router',
4
4
  requires: ['noxt-plugin'],
5
5
  provides: ['#noxt-router'],
6
- npm: {
7
- 'noxt-js-middleware': '^1.0.4'
8
- },
6
+ packages: {
7
+ noxt: 'noxt-js-middleware'
8
+ }
9
9
  }
10
10
 
11
11
  export default mlm => ({
@@ -14,12 +14,30 @@ export default mlm => ({
14
14
  is: ['string'],
15
15
  default: ['views']
16
16
  },
17
- 'middleware.noxt': async (app) => {
18
- const { default: noxt } = await mlm.import('noxt-js-middleware');
17
+ 'prod.middleware.noxt': async (app) => {
18
+ const { default: noxt } = mlm.packages.noxt;
19
+ const noxtRouter = await noxt({
20
+ context: mlm.services.noxt.context,
21
+ views: mlm.config.views,
22
+ hooks: mlm.services.noxt.hooks,
23
+ })
24
+ return noxtRouter
25
+ },
26
+ 'dev.middleware.noxt': async (app) => {
27
+ const { default: noxt } = mlm.packages.noxt;
19
28
  const noxtRouter = await noxt({
29
+ context: mlm.services.noxt.context,
20
30
  views: mlm.config.views,
21
- hooks: mlm.services.noxt.hooks
31
+ hooks: mlm.services.noxt.hooks,
32
+ watch: true
22
33
  })
23
34
  return noxtRouter
24
35
  },
36
+ 'dev.componentChanged.reload': ({ name, component }) => {
37
+ mlm.services.reload.refresh();
38
+ },
39
+ 'dev.componentError.reload': async ({ name, error }) => {
40
+ mlm.services.reload.error(error);
41
+ },
42
+ 'dev.requestContext.reload': ({ ctx }) => ctx.slot('script', 'noxt-reload', '/_reload.js'),
25
43
  })
package/units/plugin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export const info = {
2
2
  name: 'plugin',
3
3
  version: '1.0.0',
4
- description: 'Base',
4
+ description: 'Plugin base',
5
5
  requires: ['env','utils','services','hooks','config']
6
6
  }
package/units/services.js CHANGED
@@ -5,17 +5,21 @@ export const info = {
5
5
  requires: ['utils']
6
6
  }
7
7
 
8
- const services = {}
9
8
 
10
- export default mlm => ({
11
- 'define.services': () => mlm.utils.readOnly(services, 'services'),
12
- 'register.services': mlm.utils.collector(services, {
13
- is: 'function',
14
- mode: 'object',
15
- map: (fn, key) => {
16
- const service = fn(mlm)
17
- mlm.assert.is.object(service, 'service');
18
- return service
9
+ export default mlm => {
10
+ const services = {}
11
+
12
+ return {
13
+ 'define.services': () => mlm.utils.readOnly(services, 'services'),
14
+ 'collect.services': {
15
+ target: services,
16
+ is: 'function',
17
+ mode: 'object',
18
+ map: (fn, key) => {
19
+ const service = fn(mlm)
20
+ mlm.assert.is.object(service, 'service');
21
+ return service
22
+ }
19
23
  }
20
- }),
21
- })
24
+ }
25
+ }
package/units/static.js CHANGED
@@ -1,25 +1,49 @@
1
- import fs from "fs";
2
1
  export const info = {
3
2
  name: 'static',
4
3
  description: 'Static middleware',
5
4
  requires: ['express'],
5
+ packages: ['express']
6
6
  }
7
7
 
8
8
  import { resolve } from 'path';
9
+ import fs from 'fs';
9
10
 
10
- export default mlm => ({
11
- 'config.static': {
12
- is: 'string',
13
- default: 'public',
14
-
15
- },
11
+ export default mlm => {
12
+ let watcher;
13
+ return {
14
+ 'config.static': {
15
+ is: 'string',
16
+ default: 'public',
17
+ },
16
18
 
17
- 'middleware.static': async () => {
18
- const dir = mlm.config.static;
19
- if (!dir) return null;
20
- const { static: serveStatic } = await mlm.import('express');
21
- return serveStatic(resolve(dir), {
22
- setHeaders: res => res.logGroup = 'static',
23
- });
24
- },
25
- })
19
+ 'prod.middleware.static': async () => {
20
+ const dir = mlm.config.static;
21
+ if (!dir) return null;
22
+ return mlm.packages.express.static(resolve(dir), {
23
+ setHeaders: res => res.logGroup = 'static',
24
+ });
25
+ },
26
+ 'dev.middleware.static': async () => {
27
+ const dir = mlm.config.static;
28
+ if (!dir) return null;
29
+ return mlm.packages.express.static(resolve(dir), {
30
+ etag: false,
31
+ cacheControl: false,
32
+ setHeaders: res => res.logGroup = 'static',
33
+ });
34
+ },
35
+ 'dev.onStart'() {
36
+ const dir = mlm.config.static;
37
+ if (!dir) return;
38
+ watcher = fs.watch(dir, { recursive: true }, mlm.utils.debounce(() => {
39
+ mlm.services.reload.refresh()
40
+ }, 50))
41
+ },
42
+ 'dev.onStop'() {
43
+ if (watcher) {
44
+ watcher.close();
45
+ watcher = null;
46
+ }
47
+ }
48
+ }
49
+ }
package/units/utils.js CHANGED
@@ -81,20 +81,15 @@ export default mlm => {
81
81
 
82
82
  return ({
83
83
  'define.utils': () => utils.readOnly(utils, 'utils'),
84
- 'register.collect': async (confs,unit) => {
84
+ 'register.utils': utils.collector(utils, { is: 'function', mode: 'object', map: (fn, key) => { mlm.log('utils', key); return fn; } }),
85
+ 'inject.collect': async (confs,unit) => {
86
+ const ret = {}
85
87
  for (const key in confs) {
86
88
  const { target, ...conf } = confs[key];
87
89
  conf.label = key;
88
- await mlm.inject({
89
- [`register.${key}`]: utils.collector(target, conf),
90
- }, unit);
91
- if (unit[key]) {
92
- mlm.inject({
93
- [key]: unit[key]
94
- }, unit);
95
- }
90
+ ret[key] = utils.collector(target, conf);
96
91
  }
92
+ return {register:ret}
97
93
  },
98
- 'register.utils': utils.collector(utils, { is: 'function', mode: 'object', map: (fn, key) => { mlm.log('utils', key); return fn; } }),
99
94
  })
100
95
  }
@@ -1,43 +0,0 @@
1
- export const info = {
2
- name: 'noxt-router-dev',
3
- description: 'Sets up a Noxt Router',
4
- requires: ['noxt-plugin','reload'],
5
- provides: ['#noxt-router'],
6
- npm: {
7
- 'noxt-js-middleware': '^1.0.4'
8
- },
9
- }
10
-
11
- export default mlm => ({
12
- 'config.views': {
13
- normalize: p => [p].flat(),
14
- is: ['string'],
15
- default: ['views']
16
- },
17
- 'middleware.noxt': async () => {
18
- const { default: noxt } = await mlm.import('noxt-js-middleware');
19
- const noxtRouter = await noxt({
20
- context: mlm.services.noxt.context,
21
- views: mlm.config.views,
22
- hooks: mlm.services.noxt.hooks,
23
- watch: true
24
- })
25
- /*
26
- const devRouter = await noxt({
27
- context: mlm.noxt_context,
28
- views: mlm.config.views,
29
- hooks: mlm.services.noxt.noxt_hooks,
30
- noxt: noxtRouter
31
- })
32
- //noxtRouter.use('/dev', devRouter)
33
- */
34
- return noxtRouter
35
- },
36
- 'componentChanged.reload': ({ name, component }) => {
37
- mlm.services.reload.refresh();
38
- },
39
- 'componentError.reload': async ({ name, error }) => {
40
- mlm.services.reload.error(error);
41
- },
42
- 'requestContext.reload': ({ ctx }) => ctx.slot('script', 'noxt-reload', '/_reload.js'),
43
- })
@@ -1,42 +0,0 @@
1
- import fs from "fs";
2
- export const info = {
3
- name: 'static-dev',
4
- description: 'Static dev middleware',
5
- requires: ['express', 'reload'],
6
- }
7
-
8
- import { resolve } from 'path';
9
-
10
- export default mlm => {
11
- let watcher = null;
12
- return {
13
- 'config.static': {
14
- is: 'string',
15
- default: 'public',
16
-
17
- },
18
-
19
- 'middleware.static': async () => {
20
- const dir = mlm.config.static;
21
- if (!dir) return null;
22
-
23
- watcher = fs.watch(dir, { recursive: true }, mlm.utils.debounce(() => {
24
- mlm.services.reload.refresh()
25
- }, 50))
26
-
27
- const { static: serveStatic } = await mlm.import('express');
28
- return serveStatic(resolve(dir), {
29
- etag: false,
30
- cacheControl: false,
31
- setHeaders: res => res.logGroup = 'static',
32
- });
33
- },
34
- onStop() {
35
- const dir = mlm.config.static;
36
- if (watcher) {
37
- watcher.close();
38
- watcher = null;
39
- }
40
- }
41
- }
42
- }