Merging the entry's options
The generated server entry combines the file's server options with the entry's with mergeHandlerOptions, which @shadow-js/dusk/server exports.
The generated server entry combines the file's server options with the entry's with mergeHandlerOptions, which
@shadow-js/dusk/server exports. It works key by key, at every depth of a plain object:
| The file and the entry both have | Result |
|---|---|
a plain object (security, security.headers, one path of routeRules) |
merged: the entry's keys win, and the file's other keys stay |
any other value: a string, number, true, false, null, an array, a function, a class instance |
the entry's value replaces the file's |
undefined in the entry |
not set: the file's value stays, so an explicit undefined does not erase it |
A key only one side sets is kept as it is. Neither file is changed by the merge. The keys __proto__, constructor and
prototype are ignored at every depth. A merge creates at most 10 000 objects, so a cyclic object fails at start with a
RangeError instead of running on.
// dusk.config.ts
export default defineConfig({
security: { allowedHosts: ["example.com"], headers: { frameOptions: "DENY" } },
});
// src/entry-server.ts
export const options = {
security: { csp: { directives: { "default-src": ["'self'"] } } },
};
// The handler has allowedHosts and headers from the file, and csp from the entry.
Some consequences, all of them intended:
falseandnulloverride.security: { csp: false }in the entry turns off a Content-Security-Policy that the file declares.- Arrays replace and never concatenate.
security.allowedHostsin the entry replaces the file's list, so an entry that setsallowedHosts: []leaves no host check (production then also needsacknowledgeNoHostCheck: true). Middleware arrays replace as well (the file has no middleware today). Concatenation would keep a redirect or a host the entry meant to remove. security.corsmerges per key: anoriginslist in the entry replaces the file's list and the file'smethodsstay.- Routing:
redirectsand theheadersrule list replace as whole lists.rewritesmerges per phase (beforeFiles,afterFiles,fallback): the entry's array for a phase replaces that phase's array.routeRulesmerges per path, and within a path per rule key, so arevalidatefrom the file and acacheControlfrom the entry both apply. - A function (
onError,requestIdas a function) and a class instance replace whatever the file had.
Conflicts between the file and the entry#
When the same leaf is set in both files with different values, the build prints one warning per leaf, and the entry's value is the one in force:
dusk: security.headers.frameOptions is set in dusk.config.ts ("DENY") and in src/entry-server.ts ("SAMEORIGIN"). src/entry-server.ts wins; keep one of the two, or set the same value in both.
The check reads the entry as text and compares literal values: strings, numbers, booleans, null, and arrays of those. A
value the entry computes (a call, a variable that is not a literal, a spread) is not compared, so a conflict through such a
value is not reported. The merge rules above still apply to it. A file that cannot be read as TypeScript is reported as
"not compared" and the build continues to its own error.