mikser-io 9.37.1 → 9.38.1

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.
@@ -58,10 +58,10 @@ These options are part of `runtime.options` and apply to the engine itself.
58
58
  | `trace` | `-t, --trace` | boolean | `false` | Enable trace-level logging (very verbose). |
59
59
  | `threads` | — | number | `4` | Worker thread count for the Piscina pools (`renderWorkers`, `postprocessWorkers`). Both pools are lazy (`minThreads: 0` + `idleTimeout: 30_000`) so INLINE-only workloads spin up zero workers. |
60
60
  | `server` | `-s, --server [port]` | number\|boolean | — | When set, the engine creates a shared Express app on `runtime.options.app` and listens on the given port (default `3001`) after all plugins have mounted their routes. Plugins like `api` attach to it instead of starting their own server. The `outputFolder` is also served as a static catch-all route at `/` (plugin routes match first; anything that doesn't match falls through to the rendered output). Requires `express` to be installed. |
61
- | `junk` | — | array\|false | built-in list | OS and file-manager litter, filtered out of both the scan and the watcher. The dot-prefixed files (`.DS_Store`, `._*`) were already invisible — globby defaults to `dot: false` and the watcher ignores leading dots — but the Windows ones are **not** dotfiles: `Thumbs.db` and `desktop.ini` were measurably scanned *and* watched, and became entities. The list is deliberately conservative (OS/file-manager artifacts and application lock files only, no `*.tmp`, `*.bak` or editor backups), because a filter that silently drops content is worse than the litter it prevents. `false` disables it; an array replaces it. See `isJunkPath` / `JUNK_IGNORE` in `src/utils.js`. Plugins that write metadata next to content add their own patterns with `registerJunk({ ignore, match })` — the engine provides the mechanism and the plugin the knowledge of what its files are called (`mikser-io-webdav` registers `*.nephelemeta`). Plugin registrations survive an array override, since narrowing the OS list is not a request to start importing a library's sidecars. |
61
+ | `junk` | — | array\|false | built-in list | OS and file-manager litter, filtered out of both the scan and the watcher. The dot-prefixed files (`.DS_Store`, `._*`) were already invisible — globby defaults to `dot: false` and the watcher ignores leading dots — but the Windows ones are **not** dotfiles: `Thumbs.db` and `desktop.ini` were measurably scanned *and* watched, and became entities. The list is deliberately conservative (OS/file-manager artifacts and application lock files only, no `*.tmp`, `*.bak` or editor backups), because a filter that silently drops content is worse than the litter it prevents. `false` disables it; an array replaces it. See `isJunkPath` / `JUNK_IGNORE` in `src/utils.js`. Plugins that write metadata next to content add their own patterns with `registerJunk({ ignore, match })` — the engine provides the mechanism and the plugin the knowledge of what its files are called (`mikser-io-drive` registers `*.nephelemeta`). Plugin registrations survive an array override, since narrowing the OS list is not a request to start importing a library's sidecars. |
62
62
  | — (plugin) | — | object | — | `sources({ styles: { folder: 'styles', extensions: ['css'] } })` registers build inputs as catalog entities, one collection per key. A sidecar can then read them with `findEntities()`, whose queries land in the render's `refClosure` — so editing, adding or removing a part re-renders the bundle and nothing else. Reading the same files with `fs` instead works for one build and silently breaks watch, because the engine has no dependency on a file it never saw. Nothing is linked into `outputFolder`: these are inputs, not output. Named `sources` rather than `inputs` because `entity.inputs` already means something adjacent — bytes an output depends on without being entities at all. |
63
63
  | `cors` / `no-cors` | `--cors` / `--no-cors` | boolean | — | Toggle CORS on the engine's shared Express app. See `src/server.js` for the extensible header arrays plugins push onto. |
64
- | `server.requestTimeout` | — | number | node default (`300000`) | Milliseconds a single request may take, set on the underlying `http.Server`. Node's 5-minute default is effectively an **upload size limit expressed in seconds** — a large file over a slow link is indistinguishable from a stalled request, so it is cut off and the caller sees a truncated write rather than a readable error. Only reachable from the `http.Server`, which the engine owns, so a plugin that mounts an upload surface (`mikser-io-webdav`, `forms` with large attachments) cannot raise it for itself. `0` disables the cap: reasonable on a trusted-network build server, bad facing the internet, where it removes the only bound on how long a client can hold a connection open doing nothing. `headersTimeout` is clamped to stay at or below it. The server is also exposed as `runtime.options.httpServer`. |
64
+ | `server.requestTimeout` | — | number | node default (`300000`) | Milliseconds a single request may take, set on the underlying `http.Server`. Node's 5-minute default is effectively an **upload size limit expressed in seconds** — a large file over a slow link is indistinguishable from a stalled request, so it is cut off and the caller sees a truncated write rather than a readable error. Raised to two hours automatically when any route registers as `streaming` (an upload surface such as `mikser-io-drive`), because Node's default mismeasures exactly those; set this explicitly to override. `0` disables the cap: reasonable on a trusted-network build server, bad facing the internet, where it removes the only bound on how long a client can hold a connection open doing nothing. `headersTimeout` is clamped to stay at or below it. The server is also exposed as `runtime.options.httpServer`. |
65
65
  | `url` | `-u, --url <url>` | string | — | Public URL where this mikser is reachable (e.g. `https://blog.me.com`). Validated, trailing slash stripped, stamped on `runtime.options.url`. Read by webhook-capable plugins for push-vs-poll gating (`url.startsWith('https://')`); used by anything that surfaces absolute URLs externally — MCP preview URLs returned to agents, forms share links, email tracking pixels. Plugins that just need internal URLs keep using `runtime.options.port`. |
66
66
 
67
67
  ## Engine Substrate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikser-io",
3
- "version": "9.37.1",
3
+ "version": "9.38.1",
4
4
  "description": "<p align=\"center\"> <img src=\"mikser-lockup-stacked.svg\" alt=\"mikser\" width=\"198\" /> </p>",
5
5
  "main": "index.js",
6
6
  "exports": {
package/src/server.js CHANGED
@@ -31,6 +31,11 @@ import { onInitialized, onLoad, onLoaded } from './lifecycle.js'
31
31
  // engine.js's onInitialize callback AFTER engine's own options chain
32
32
  // and BEFORE the .parse() call — preserves the order of options in
33
33
  // --help output.
34
+ // Two hours. Long enough for a gigabyte at 1.2 Mbps, which is below any link
35
+ // this runs on, and short enough to remain a bound. Applied only when a
36
+ // streaming route exists, so a plain build server keeps Node's default.
37
+ const STREAMING_REQUEST_TIMEOUT = 2 * 60 * 60 * 1000
38
+
34
39
  export function attachServerCliOptions(commander) {
35
40
  commander
36
41
  ?.option('-s --server [port]', 'start an Express server on the given port (defaults to 3001)')
@@ -224,11 +229,25 @@ export function setupServer() {
224
229
  // large attachments) cannot raise it for itself. Hence a
225
230
  // config knob here rather than in the plugin.
226
231
  //
227
- // `0` disables the cap entirely. That is a deliberate choice
232
+ // Raised automatically when a streaming route is mounted; see
233
+ // below. `0` disables the cap entirely. That is a deliberate choice
228
234
  // for a trusted-network build server and a bad one facing the
229
235
  // internet, where it removes the only bound on how long a
230
236
  // client can hold a connection open doing nothing.
231
- const requestTimeout = runtime.config.server?.requestTimeout
237
+ // A streaming route is one a facade must not buffer — an
238
+ // upload surface, an SSE stream — and it is exactly the shape
239
+ // Node's 5-minute cap mismeasures. `registerRoute` already
240
+ // records which routes those are, so the engine can tell
241
+ // without being told twice.
242
+ //
243
+ // Two hours rather than `0`: a 1GB upload needs better than
244
+ // 1.2 Mbps to fit, which any real link clears, while a finite
245
+ // bound still stops a client holding a connection open
246
+ // forever. Disabling it entirely stays available and stays a
247
+ // deliberate choice.
248
+ const streaming = (runtime.routes ?? []).filter(r => r.streaming)
249
+ const configured = runtime.config.server?.requestTimeout
250
+ const requestTimeout = configured ?? (streaming.length ? STREAMING_REQUEST_TIMEOUT : undefined)
232
251
  if (requestTimeout != null) {
233
252
  httpServer.requestTimeout = requestTimeout
234
253
  // headersTimeout must not exceed requestTimeout, or Node
@@ -237,8 +256,12 @@ export function setupServer() {
237
256
  if (requestTimeout !== 0) {
238
257
  httpServer.headersTimeout = Math.min(httpServer.headersTimeout, requestTimeout)
239
258
  }
240
- logger.info('Server request timeout: %s',
241
- requestTimeout === 0 ? 'disabled' : `${requestTimeout}ms`)
259
+ logger.info('Server request timeout: %s%s',
260
+ requestTimeout === 0 ? 'disabled' : `${Math.round(requestTimeout / 60000)}m`,
261
+ configured == null
262
+ ? ` (raised from Node's 5m default for ${streaming.length} streaming route(s): `
263
+ + `${streaming.map(r => r.path).join(', ')} — set config.server.requestTimeout to override)`
264
+ : '')
242
265
  }
243
266
  runtime.options.httpServer = httpServer
244
267
  })
package/src/utils.js CHANGED
@@ -1079,7 +1079,7 @@ export function isFullCycle(rt = runtime) {
1079
1079
 
1080
1080
  // ── operating-system and file-manager litter ────────────────────────────
1081
1081
  //
1082
- // Exposing a source folder over a network filesystem (mikser-io-webdav) or
1082
+ // Exposing a source folder over a network filesystem (mikser-io-drive) or
1083
1083
  // simply opening it in a file manager drops metadata files into it. Every one
1084
1084
  // of them would otherwise become an entity, render a page, and appear in a
1085
1085
  // catalog.
@@ -1146,7 +1146,7 @@ export const JUNK_IGNORE = [
1146
1146
  // the engine has no business knowing what a particular library's sidecar file
1147
1147
  // is called. What it can provide is the mechanism: a plugin that writes
1148
1148
  // metadata next to content says so, and both the scan and the watcher honour
1149
- // it. (mikser-io-webdav registers `*.nephelemeta` for exactly this reason:
1149
+ // it. (mikser-io-drive registers `*.nephelemeta` for exactly this reason:
1150
1150
  // the collection-level file is dot-prefixed and was already invisible, while
1151
1151
  // the per-file one — `page.md.nephelemeta` — is not, and was measurably
1152
1152
  // becoming an entity.)