@nifrajs/web 5.3.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # @nifrajs/web
2
2
 
3
+ ## 5.4.0
4
+
5
+ ### Patch Changes
6
+
7
+ - e7d43ca: On Bun, `servePublicDir` now sends `content-length` for files up to 1 MiB, whole or ranged. This covers the Bun server entry and `nifra dev`. `Bun.serve` drops the declared length of a stream body and sends it chunked, so these files used to arrive with no length: no download progress, and caches and proxies could not size them. They are now read whole through the already-open file, as files up to 256 KiB already were, which serves a 1 MiB file about a third faster. Files above 1 MiB still stream, and Bun still sends them chunked. Node and Deno keep a stream's length and are unchanged.
8
+ - 5183c00: The server entry `buildTarget` generates for `deno` now answers static files with the validators the Bun and Node entries send:
9
+
10
+ - an `etag` and `last-modified`, and a `304` with no body for a current copy, HEAD included.
11
+ - `Range` honored only when `If-Range` is absent or names the file's current validator; anything else gets the whole file.
12
+ - a link is typed by the file it resolves to, as on Bun and Node.
13
+
14
+ `@nifrajs/core/static-file` holds the comparisons `servePublicDir`, the Deno entry and `@nifrajs/node`'s `static` reader now share.
15
+ - e589893: The server entry `buildTarget` generates for `node` now passes the app itself to `@nifrajs/node`'s `serve` and serves files with the adapter's own reader. The entry needs the `@nifrajs/node` of the same release, so a `node` build with an older one installed stops before bundling, names both versions and leaves the last deploy dir in place. A 5.3 adapter used to build cleanly and then throw when the server started. It used to wrap `app.fetch` in a plain `{ fetch }` handler. That wrapper hid the node-direct lane, so every page built an undici `Request` and drained a `Response`, and every file went through one too. The entry mounts `/assets` and, at the root, only the `public/` files the build copied, with the same type, cache, range, HEAD and hidden-file answers the Bun entry gives.
16
+
17
+ `serve`'s `static` option on `@nifrajs/node`:
18
+
19
+ - takes an array of mounts; a path goes to the first mount that takes it, by its prefix and, when set, its `files`.
20
+ - `files` lists the only URL paths a mount serves; any other path passes to the next mount, then the app, without that mount touching the disk.
21
+ - `cacheControl` sets `cache-control` for files not served immutable.
22
+ - types files from the same table as `servePublicDir`, so `.pdf`, `.mp4`, `.xml`, `.csv` and other common types are no longer sent as `application/octet-stream`; a link is typed by the file it resolves to.
23
+ - serves `/.well-known` by default, as `servePublicDir` does, and refuses a visible name that links to a dotfile.
24
+ - answers a link that leaves the directory with `404`, the same as a missing file, instead of `403`.
25
+ - sends files up to 256 KiB, and HEAD, `304` and `416` answers, in one write with no `Response` or stream.
26
+
27
+ `@nifrajs/core/static-file` exports `CONTENT_TYPES`, `contentTypeOf` and `isHiddenPath`, which both readers now use.
28
+ - 02ac072: Static files on Bun and Deno evaluate `If-None-Match` before `If-Modified-Since`, including the wildcard condition for an existing file. A stale entity tag can no longer produce a `304` from its accompanying date.
29
+ - 529f8d2: Static files are tagged by their content on every self-hosting entry: `servePublicDir` (the Bun entry and `nifra dev`), `@nifrajs/node`'s `static` reader and the generated Deno entry. Each sends a strong `ETag` of 128 bits of the file's SHA-256, the same for the same bytes on every instance and runtime. A replacement that keeps a file's size and modification time (`cp -p`, `rsync -a`, tar, a container image layer, SOURCE_DATE_EPOCH, the Nix store's 1 s) is no longer answered with a `304` for the old copy, and a resumed download is no longer spliced from two versions. The Node reader's `"<mtime>-<size>"` tag is gone; the Bun and Deno entries send a tag for the first time.
30
+
31
+ - A tag is remembered per process for the file version it was read from (device, inode, size, mtime and ctime), so a known file costs no extra read. The first request for a file reads it once, and a file within the buffered size is then served from those bytes. Concurrent first requests share one read. A file changed in the last 2 s is read again until its ctime settles. A file over 256 MiB (`CONTENT_TAG_MAX`) is tagged by that identity instead: still strong, but different on each instance.
32
+ - `Last-Modified` is sent only while the file's mtime is the time of its own last change (within 1 s of its ctime). A file whose mtime a copy or build set, such as every file in a container image, is sent without it, so `If-Modified-Since` cannot validate it.
33
+ - An `If-Range` date no longer keeps a range for a file with a strong tag; a client resumes by the tag. On Node a configured strong `ETag` likewise sends the whole file for a date, while a configured weak one still lets a configured `Last-Modified` resume by its exact date.
34
+ - `@nifrajs/core/static-file` exports `contentTags`, `CONTENT_TAG_MAX`, `lastModifiedOf` and `isNotModified`, the `If-None-Match` and `If-Modified-Since` check all three readers share. `ifRangeAllowsPartial` now takes the file's tag.
35
+ - Updated dependencies [5183c00]
36
+ - Updated dependencies [01b97ec]
37
+ - Updated dependencies [e589893]
38
+ - Updated dependencies [ff66db6]
39
+ - Updated dependencies [529f8d2]
40
+ - @nifrajs/core@5.4.0
41
+ - @nifrajs/island-trigger@5.4.0
42
+
3
43
  ## 5.3.0
4
44
 
5
45
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAyB3D,OAAO,EAAE,KAAK,SAAS,EAAQ,MAAM,KAAK,CAAA;AAC1C,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,WAAW,EAEhB,KAAK,OAAO,EAaZ,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,iBAAiB,CAAA;AAExB,OAAO,EAAE,KAAK,cAAc,EAA8C,MAAM,mBAAmB,CAAA;AAEnG,OAAO,EAAuB,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAUxE,OAAO,EACL,KAAK,qBAAqB,EAE3B,MAAM,iCAAiC,CAAA;AAGxC,OAAO,EAQL,KAAK,eAAe,EAIrB,MAAM,2BAA2B,CAAA;AAyBlC,cAAc,iBAAiB,CAAA;AA+B/B,YAAY,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AAE5E,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAG5E,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,4BAA4B,EACjC,qBAAqB,EACrB,WAAW,EACX,WAAW,EACX,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,eAAe,GAChB,MAAM,gBAAgB,CAAA;AAEvB,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,gGAAgG;IAChG,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IAC3C,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;IACvC,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,qFAAqF;IACrF,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAClD,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,oGAAoG;IACpG,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACnC;;;;;;;;uCAQmC;IACnC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IACtD;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAC7B;AAiBD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,mGAAmG;IACnG,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CACpC;AA2DD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,SAAS,MAAM,EAAE;AAC9B;;;;GAIG;AACH,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,oBAAoB,CAgCtB;AAED,8FAA8F;AAC9F;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAsB/E;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,EACtD,YAAY,EAAE,MAAM,GACnB,MAAM,CAYR;AAED;;;;;;GAMG;AACH;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAmYrF;AAED,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;kHAC8G;IAC9G,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB;0FACsF;IACtF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B;;oEAEgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IAC/C,0GAA0G;IAC1G,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IAC9E,sGAAsG;IACtG,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAA;IAClE,oGAAoG;IACpG,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAA;IACpC;gFAC4E;IAC5E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IAC3C;wFACoF;IACpF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B;gEAC4D;IAC5D,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;IACvC;kCAC8B;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC;+FAC2F;IAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAClD,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB;;;;6BAIyB;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,KAAK,CAAA;IAC5C;;;;2GAIuG;IACvG,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,iGAAiG;IACjG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;gGAC4F;IAC5F,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3C;8CAC0C;IAC1C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAClC;AA+BD,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,KAAG,SAc/C,CAAA;AAcF,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,SAchD,CAAA;AAGF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,SAgDhD,CAAA;AAuFF,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAEzD;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,QAAO,SAWpC,CAAA;AAqBF;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CAiHnF;AAWD,sGAAsG;AACtG,eAAO,MAAM,oBAAoB,sEAOvB,CAAA;AACV,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAA;AACrE,oFAAoF;AACpF,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;AAE3F;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAA;AAgB5C;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,sFAAsF;IACtF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,mGAAmG;IACnG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B;mDAC+C;IAC/C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B;0BACsB;IACtB,QAAQ,CAAC,aAAa,CAAC,EAAE,wBAAwB,CAAA;IACjD,mDAAmD;IACnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,gFAAgF;IAChF,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC;kGAC8F;IAC9F,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,yGAAyG;IACzG,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IACvC,+FAA+F;IAC/F,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;IAC7B;oFACgF;IAChF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAA;CAC5E,GAAG,MAAM,CAkOT;AA+GD,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;;wEAEoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB;2FACuF;IACvF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,uGAAuG;IACvG,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,sFAAsF;IACtF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,sGAAsG;IACtG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B;;oCAEgC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B;8GAC0G;IAC1G,QAAQ,CAAC,aAAa,CAAC,EAAE,wBAAwB,CAAA;IACjD;;;;;;4DAMwD;IACxD,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC/F,iEAAiE;IACjE,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;IAC7C,oDAAoD;IACpD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;IAC7C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAClD,gFAAgF;IAChF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACnC,4FAA4F;IAC5F,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,oGAAoG;IACpG,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IACtD;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAA;IACpC,yDAAyD;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB;+FAC2F;IAC3F,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAA;CACxC;AAED,wGAAwG;AACxG,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CAClD;AAED,qGAAqG;AACrG,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,sCAAsC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB;kCAC8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,iFAAiF;AACjF,eAAO,MAAM,UAAU,EAAE,OAsCxB,CAAA;AAgED,uGAAuG;AACvG,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,iBAAiB,CAAC,CAE5B;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,iBAAiB,CAAC,CA8N5B"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
package/dist/build.js CHANGED
@@ -11,6 +11,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync
11
11
  import { cp, lstat, mkdir, realpath } from "node:fs/promises";
12
12
  import { dirname, isAbsolute, join, basename as pathBasename, posix, relative, resolve as resolvePath, sep, } from "node:path";
13
13
  import { singleCopyPlugin as declaredSingleCopyPlugin, readSingleCopyDeclaration, } from "@nifrajs/core/single-copy";
14
+ import { isHiddenPath } from "@nifrajs/core/static-file";
14
15
  import { Glob } from "bun";
15
16
  import { aggregateSizeReport, detectNodeBuiltinsInClient, detectServerOnlyInClient, formatNodeBuiltinLeak, formatServerOnlyLeak, parseBuildTarget, parseManifestClientEntry, parseManifestCopyChunks, parseManifestCssLoading, parseManifestRouteStyles, parseManifestStyles, planBuildTarget, } from "./build-plan.js";
16
17
  import { sanitizeOutputNames } from "./chunk-names.js";
@@ -21,10 +22,11 @@ import { loadCopySplit } from "./internal/copy-split.js";
21
22
  import { assertDisposableDir } from "./internal/disposable-dir.js";
22
23
  import { entryChunks } from "./internal/entry-chunks.js";
23
24
  import { dedupePolicyFor } from "./internal/identity-policy.js";
25
+ import { nodeAdapterRefusal } from "./internal/node-adapter.js";
24
26
  import { assertDevelopmentProductionParity, assertIdentityParity, collectDevelopmentParityInput, } from "./internal/parity.js";
25
27
  import { servablePrerenderedPages, } from "./internal/prerendered-pages.js";
26
28
  import { privateEnvCheck } from "./internal/private-env.js";
27
- import { isHiddenPublicPath, publicUrlPath } from "./internal/public-url.js";
29
+ import { publicUrlPath } from "./internal/public-url.js";
28
30
  import { assertNoSecrets, buildEnvironment, emittedScanFiles, formatSecretFindings, graphScanInput, originName, publicScanFiles, SecretScanError, scanForSecrets, textOf, } from "./internal/secret-scan.js";
29
31
  import { generateServerFnStub, SERVER_FN_MODULE, serverFnNamespace, } from "./internal/server-boundary.js";
30
32
  import { formatUnsupportedBuiltins, unsupportedBuiltins } from "./internal/target-compat.js";
@@ -34,7 +36,7 @@ import { assertNoImportGlob, importGlobPlugin } from "./plugins/import-glob.js";
34
36
  import { zoneGuardPlugin } from "./plugins/zone-guard.js";
35
37
  // `buildTarget(static)` drives the SSG prerender engine directly (it's also re-exported below).
36
38
  import { prerenderRoutes } from "./prerender.js";
37
- import { CONTENT_TYPES, IMMUTABLE, ONE_DAY } from "./public-dir.js";
39
+ import { IMMUTABLE, ONE_DAY } from "./public-dir.js";
38
40
  import { createZoneClassifier } from "./zones.js";
39
41
  export * from "./build-plan.js";
40
42
  const basename = (path) => pathBasename(path);
@@ -180,7 +182,7 @@ export async function copyPublicDir(from, to) {
180
182
  const sourceReal = await realpath(source);
181
183
  const copied = [];
182
184
  for await (const rel of new Glob("**/*").scan({ cwd: source, dot: true, onlyFiles: false })) {
183
- if (isHiddenPublicPath(rel))
185
+ if (isHiddenPath(rel))
184
186
  continue;
185
187
  const candidate = join(source, rel);
186
188
  const candidateStat = await lstat(candidate);
@@ -1023,20 +1025,20 @@ export function generateServerEntry(options) {
1023
1025
  if (target === "static") {
1024
1026
  throw new Error("[nifra/web] generateServerEntry: `static` has no server entry (SSG only)");
1025
1027
  }
1026
- // Bun and Node serve the client bundle through `servePublicDir`. Deno bundles for the browser
1027
- // condition, where `node:fs` is a stub, so its entry runs the same checks on Deno's own file API.
1028
+ // Bun serves the client bundle through `servePublicDir` and Node through @nifrajs/node's own reader,
1029
+ // both on core's static-file policy. Deno bundles for the browser condition, where `node:fs` is a
1030
+ // stub, so its entry runs the same checks on Deno's own file API.
1028
1031
  const nodeFs = target === "bun" || target === "node";
1029
1032
  const prerendered = Object.entries(options.prerenderedPages ?? {});
1030
1033
  if (prerendered.length > 0 && !nodeFs) {
1031
1034
  throw new Error(`[nifra/web] generateServerEntry: prerendered pages are served by the bun and node targets, not ${target}`);
1032
1035
  }
1033
- const lines = [
1034
- !nodeFs
1035
- ? 'import { createWebApp } from "@nifrajs/web"'
1036
- : prerendered.length > 0
1037
- ? 'import { createWebApp, prerenderedPageReader, servePublicDir } from "@nifrajs/web"'
1038
- : 'import { createWebApp, servePublicDir } from "@nifrajs/web"',
1039
- ];
1036
+ const webImports = ["createWebApp"];
1037
+ if (prerendered.length > 0)
1038
+ webImports.push("prerenderedPageReader");
1039
+ if (target === "bun")
1040
+ webImports.push("servePublicDir");
1041
+ const lines = [`import { ${webImports.join(", ")} } from "@nifrajs/web"`];
1040
1042
  if (backendImport !== undefined)
1041
1043
  lines.push('import { inProcessClient } from "@nifrajs/client"');
1042
1044
  lines.push(`import { adapter } from ${JSON.stringify(adapterImport)}`);
@@ -1058,8 +1060,9 @@ export function generateServerEntry(options) {
1058
1060
  lines.push('import { serve } from "@nifrajs/node"');
1059
1061
  if (nodeFs)
1060
1062
  lines.push('import { fileURLToPath } from "node:url"');
1061
- if (target === "deno")
1062
- lines.push('import { parseByteRange } from "@nifrajs/core/range"');
1063
+ if (target === "deno") {
1064
+ lines.push('import { parseByteRange } from "@nifrajs/core/range"', 'import { contentTags, contentTypeOf, ifRangeAllowsPartial, isHiddenPath, isNotModified, lastModifiedOf } from "@nifrajs/core/static-file"', 'import { createHash } from "node:crypto"');
1065
+ }
1063
1066
  if (prerendered.length > 0) {
1064
1067
  const headers = Object.fromEntries(prerendered.map(([path, page]) => [path, page.headers]));
1065
1068
  const files = Object.fromEntries(prerendered.map(([path, page]) => [path, page.file]));
@@ -1088,12 +1091,19 @@ export function generateServerEntry(options) {
1088
1091
  // bun / node / deno self-host AND serve the client bundle from disk (it sits next to this entry).
1089
1092
  lines.push("// The client bundle and public files live next to this entry; serve only manifest-approved paths.", 'const STATIC_ROOT = new URL("./", import.meta.url)', `const PUBLIC_FILES = new Set(${JSON.stringify(publicFiles)})`, ...(hashedFiles !== undefined
1090
1093
  ? [`const HASHED_FILES = new Set(${JSON.stringify(hashedFiles)})`]
1091
- : []), "const staticPath = (pathname: string): string | undefined => {", " if (PUBLIC_FILES.has(pathname)) return '.' + pathname", ' if (!pathname.startsWith("/assets/")) return undefined', ' const segments = pathname.slice(1).split("/")', " // `.` and `..` match the character class, and the path is joined onto a base URL that resolves", " // dot segments - so they are rejected explicitly rather than relying on the request URL having", " // been normalized upstream.", " const safe = (segment: string): boolean =>", ' segment !== "." && segment !== ".." && /^[A-Za-z0-9._-]+$/.test(segment)', " return segments.every(safe) ? '.' + pathname : undefined", "}",
1092
- // The handler `nifra dev` serves public/ with: its type table, nosniff and cache policy, HEAD
1093
- // answered without reading the file, byte ranges and conditional requests, and a descriptor opened
1094
- // only once the real path is inside the root, so a link planted in the deployed tree cannot reach
1095
- // a file outside it.
1096
- ...(nodeFs
1094
+ : []));
1095
+ if (target === "node") {
1096
+ // The app itself with the adapter's own reader, not a `{ fetch }` wrapper around app.fetch: the
1097
+ // wrapper hid the node-direct lane, so every page built an undici Request and drained a Response,
1098
+ // and every file went through one too. The root mount serves only the files the build listed.
1099
+ lines.push("await serve(app, {", " port: Number(process.env.PORT ?? 3000),", " static: [", " {", ' dir: fileURLToPath(new URL("./assets/", import.meta.url)),', ' prefix: "/assets",', ...(hashedFiles !== undefined ? [" hashedFiles: HASHED_FILES,"] : []), ` cacheControl: ${JSON.stringify(ONE_DAY)},`, " },", " {", " dir: fileURLToPath(STATIC_ROOT),", ' prefix: "/",', " files: PUBLIC_FILES,", " immutable: false,", ` cacheControl: ${JSON.stringify(ONE_DAY)},`, " },", " ],", "})");
1100
+ return `${lines.join("\n")}\n`;
1101
+ }
1102
+ lines.push("const staticPath = (pathname: string): string | undefined => {", " if (PUBLIC_FILES.has(pathname)) return '.' + pathname", ' if (!pathname.startsWith("/assets/")) return undefined', ' const segments = pathname.slice(1).split("/")', " // `.` and `..` match the character class, and the path is joined onto a base URL that resolves", " // dot segments - so they are rejected explicitly rather than relying on the request URL having", " // been normalized upstream.", " const safe = (segment: string): boolean =>", ' segment !== "." && segment !== ".." && /^[A-Za-z0-9._-]+$/.test(segment)', " return segments.every(safe) ? '.' + pathname : undefined", "}",
1103
+ // The handler `nifra dev` serves public/ with: its type table, nosniff and cache policy, content
1104
+ // tags, byte ranges and conditional requests, and a descriptor opened only once the real path is
1105
+ // inside the root, so a link planted in the deployed tree cannot reach a file outside it.
1106
+ ...(target === "bun"
1097
1107
  ? [
1098
1108
  "const serveStatic = servePublicDir({",
1099
1109
  " dir: fileURLToPath(STATIC_ROOT),",
@@ -1115,43 +1125,44 @@ export function generateServerEntry(options) {
1115
1125
  `console.log(\`nifra (Bun) → http://localhost:$${"{server.port}"}\`)`);
1116
1126
  return `${lines.join("\n")}\n`;
1117
1127
  }
1118
- if (target === "node") {
1119
- lines.push("await serve(", " {", " async fetch(req, platform) {", " const { pathname } = new URL(req.url)", " if (staticPath(pathname) !== undefined) return staticResponse(req)", " return app.fetch(req, platform)", " },", " },", " { port: Number(process.env.PORT ?? 3000) },", ")");
1120
- return `${lines.join("\n")}\n`;
1121
- }
1122
1128
  // deno
1123
1129
  lines.push("// @ts-ignore - Deno global is present on the Deno runtime this output targets.", 'Deno.serve({ port: Number(Deno.env.get("PORT") ?? "3000") }, async (req, info) => {', " const { pathname } = new URL(req.url)", " const filePath = staticPath(pathname)", " if (filePath !== undefined) {", " return staticResponse(req, filePath, pathname)", " }", ` ${markFramed}`, " return app.fetch(req, { clientIp: info.remoteAddr.hostname })", "})");
1124
1130
  return `${lines.join("\n")}\n`;
1125
1131
  }
1126
1132
  /**
1127
- * The Deno entry's static handler: `servePublicDir`'s checks on Deno's file API. The real path must sit
1128
- * inside the root and cross no hidden segment, and the opened file must still be the one that path
1129
- * names, so a link planted in the deployed tree cannot reach a file outside it or a `.env` inside it.
1130
- * HEAD answers from `stat` without reading; one byte range is served from a seek, several as the
1131
- * whole file.
1133
+ * The Deno entry's static handler: `servePublicDir`'s checks and answers on Deno's file API. The real
1134
+ * path must sit inside the root and cross no hidden segment, and the opened file must still be the one
1135
+ * that path names, so a link planted in the deployed tree cannot reach a file outside it or a `.env`
1136
+ * inside it. The file is typed by its real path, tagged by its content and dated only by an mtime its
1137
+ * own last write set, through the same policy as servePublicDir: a current cached copy is a 304, and a
1138
+ * range survives only an `If-Range` naming that tag. A file is read once to tag it, and one small
1139
+ * enough is then served from those bytes; otherwise one byte range is served from a seek, several as
1140
+ * the whole file.
1132
1141
  */
1133
1142
  function denoStaticResponse(hashed) {
1134
1143
  return [
1135
- `const TYPES: Record<string, string> = ${JSON.stringify(CONTENT_TYPES)}`,
1136
- "const staticHeaders = (pathname: string): Record<string, string> => ({",
1137
- ' "content-type": TYPES[pathname.slice(pathname.lastIndexOf(".")).toLowerCase()] ?? "application/octet-stream",',
1144
+ "const staticHeaders = (pathname: string, file: string): Record<string, string> => ({",
1145
+ ' "content-type": contentTypeOf(file),',
1138
1146
  ` "cache-control": ${hashed ? "HASHED_FILES.has(pathname)" : 'pathname.startsWith("/assets/")'} ? ${JSON.stringify(IMMUTABLE)} : ${JSON.stringify(ONE_DAY)},`,
1139
1147
  ' "x-content-type-options": "nosniff",',
1140
1148
  ' "accept-ranges": "bytes",',
1141
1149
  "})",
1142
1150
  "// @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1143
1151
  "const STATIC_REAL: string = await Deno.realPath(STATIC_ROOT)",
1144
- "const hiddenSegment = (rel: string): boolean =>",
1145
- ' rel.split(/[\\\\/]/).some((segment) => segment.startsWith(".") && segment !== ".well-known")',
1152
+ "// One per process: the tag of a file is remembered by its real path.",
1153
+ "const STATIC_TAGS = contentTags(createHash)",
1154
+ "// Up to this many bytes are read whole to tag a file, and the response is then served from them.",
1155
+ "const STATIC_INLINE_MAX = 256 * 1024",
1146
1156
  "const staticResponse = async (req: Request, filePath: string, pathname: string): Promise<Response> => {",
1147
1157
  ' const missing = () => new Response("not found", { status: 404 })',
1148
1158
  ' if (req.method !== "GET" && req.method !== "HEAD") return missing()',
1149
1159
  " let file, info",
1160
+ ' let real = ""',
1150
1161
  " try {",
1151
1162
  " // @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1152
- " const real: string = await Deno.realPath(new URL(filePath, STATIC_ROOT))",
1163
+ " real = await Deno.realPath(new URL(filePath, STATIC_ROOT))",
1153
1164
  ' if (!real.startsWith(STATIC_REAL + (STATIC_REAL.includes("\\\\") ? "\\\\" : "/"))) return missing()',
1154
- " if (hiddenSegment(real.slice(STATIC_REAL.length + 1))) return missing()",
1165
+ " if (isHiddenPath(real.slice(STATIC_REAL.length + 1))) return missing()",
1155
1166
  " // @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1156
1167
  " file = await Deno.open(real)",
1157
1168
  " // Deno.open follows links, so a directory swapped for one after the check above redirects it.",
@@ -1169,12 +1180,61 @@ function denoStaticResponse(hashed) {
1169
1180
  " file?.close()",
1170
1181
  " return missing()",
1171
1182
  " }",
1183
+ " if (!info.isFile) {",
1184
+ " file.close()",
1185
+ " return missing()",
1186
+ " }",
1172
1187
  " const size: number = info.size",
1173
- " const headers = new Headers(staticHeaders(pathname))",
1174
- ' const range = req.method === "GET" ? parseByteRange(req.headers.get("range"), size) : { kind: "none" as const }',
1175
- ' if (!info.isFile || req.method === "HEAD" || range.kind === "unsatisfiable") {',
1188
+ " const headers = new Headers(staticHeaders(pathname, real))",
1189
+ " // Deno reports a field the platform does not keep as null; NaN never matches a remembered version.",
1190
+ " const version = {",
1191
+ " dev: info.dev,",
1192
+ " ino: info.ino ?? Number.NaN,",
1193
+ " size,",
1194
+ " mtimeMs: info.mtime?.getTime() ?? Number.NaN,",
1195
+ " ctimeMs: info.ctime?.getTime() ?? Number.NaN,",
1196
+ " }",
1197
+ " let whole: Uint8Array<ArrayBuffer> | undefined",
1198
+ " let moved = false",
1199
+ " let etag: string | undefined",
1200
+ " try {",
1201
+ " etag = STATIC_TAGS.cached(real, version)",
1202
+ " if (etag === undefined && size <= STATIC_INLINE_MAX) {",
1203
+ " whole = new Uint8Array(size)",
1204
+ " let filled = 0",
1205
+ " while (filled < size) {",
1206
+ " const read: number | null = await file.read(whole.subarray(filled))",
1207
+ " if (read === null || read === 0) break",
1208
+ " filled += read",
1209
+ " }",
1210
+ " if (filled < size) whole = whole.subarray(0, filled)",
1211
+ " etag = STATIC_TAGS.ofBytes(real, version, whole)",
1212
+ " }",
1213
+ " etag ??= await STATIC_TAGS.ofFile(real, version, async (bytes: Uint8Array, position: number) => {",
1214
+ " moved = true",
1215
+ " // @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1216
+ " await file.seek(position, Deno.SeekMode.Start)",
1217
+ " return (await file.read(bytes)) ?? 0",
1218
+ " })",
1219
+ " } catch {",
1220
+ " file.close()",
1221
+ " return missing()",
1222
+ " }",
1223
+ ' if (etag !== undefined) headers.set("etag", etag)',
1224
+ " const lastModified = lastModifiedOf(version)",
1225
+ ' if (lastModified !== undefined) headers.set("last-modified", new Date(lastModified).toUTCString())',
1226
+ ' if (isNotModified(req.headers.get("if-none-match"), req.headers.get("if-modified-since"), etag, lastModified)) {',
1227
+ " file.close()",
1228
+ ' headers.delete("content-type")',
1229
+ " return new Response(null, { status: 304, headers })",
1230
+ " }",
1231
+ ' const rangeHeader = req.method === "GET" ? req.headers.get("range") : null',
1232
+ " const range =",
1233
+ ' rangeHeader !== null && ifRangeAllowsPartial(req.headers.get("if-range"), etag, lastModified)',
1234
+ " ? parseByteRange(rangeHeader, size)",
1235
+ ' : { kind: "none" as const }',
1236
+ ' if (req.method === "HEAD" || range.kind === "unsatisfiable") {',
1176
1237
  " file.close()",
1177
- " if (!info.isFile) return missing()",
1178
1238
  ' if (range.kind !== "unsatisfiable") {',
1179
1239
  ' headers.set("content-length", String(size))',
1180
1240
  " return new Response(null, { headers })",
@@ -1185,10 +1245,16 @@ function denoStaticResponse(hashed) {
1185
1245
  " }",
1186
1246
  ' if (range.kind === "satisfiable" && range.ranges.length === 1) {',
1187
1247
  " const { start, end } = range.ranges[0]!",
1248
+ ' headers.set("content-range", "bytes " + start + "-" + end + "/" + size)',
1249
+ " if (whole !== undefined) {",
1250
+ " file.close()",
1251
+ " const part = whole.subarray(start, end + 1)",
1252
+ ' headers.set("content-length", String(part.byteLength))',
1253
+ " return new Response(part, { status: 206, headers })",
1254
+ " }",
1188
1255
  " // @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1189
1256
  " await file.seek(start, Deno.SeekMode.Start)",
1190
1257
  " let left = end - start + 1",
1191
- ' headers.set("content-range", "bytes " + start + "-" + end + "/" + size)',
1192
1258
  ' headers.set("content-length", String(left))',
1193
1259
  " const body = new ReadableStream<Uint8Array>({",
1194
1260
  " async pull(controller) {",
@@ -1210,6 +1276,13 @@ function denoStaticResponse(hashed) {
1210
1276
  " })",
1211
1277
  " return new Response(body, { status: 206, headers })",
1212
1278
  " }",
1279
+ " if (whole !== undefined) {",
1280
+ " file.close()",
1281
+ ' headers.set("content-length", String(whole.byteLength))',
1282
+ " return new Response(whole, { headers })",
1283
+ " }",
1284
+ " // @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1285
+ " if (moved) await file.seek(0, Deno.SeekMode.Start)",
1213
1286
  ' headers.set("content-length", String(size))',
1214
1287
  " return new Response(file.readable, { headers })",
1215
1288
  "}",
@@ -1350,6 +1423,12 @@ export async function buildTargetWith(target, options, bundler) {
1350
1423
  ];
1351
1424
  assertDisposableDir("outDir", outDir, sources);
1352
1425
  assertDisposableDir("workDir", workDir, sources);
1426
+ // Before anything is cleared, so a refused build leaves the last deploy dir in place.
1427
+ if (targetPlan.target === "node") {
1428
+ const refusal = nodeAdapterRefusal(workDir);
1429
+ if (refusal !== undefined)
1430
+ throw new Error(refusal);
1431
+ }
1353
1432
  const { rmSync } = await import("node:fs");
1354
1433
  rmSync(outDir, { recursive: true, force: true });
1355
1434
  rmSync(workDir, { recursive: true, force: true });