@webjsdev/server 0.8.13 → 0.8.15
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/index.d.ts +650 -0
- package/package.json +15 -4
- package/src/actions.js +2 -1
- package/src/asset-hash.js +266 -0
- package/src/check.d.ts +38 -0
- package/src/dev.js +87 -16
- package/src/importmap.js +68 -9
- package/src/ssr.js +75 -7
- package/src/testing.d.ts +123 -0
package/src/importmap.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { digestHex } from './crypto-utils.js';
|
|
4
4
|
import { jsonForScriptTag } from './script-tag-json.js';
|
|
5
5
|
import { withBasePath } from './base-path.js';
|
|
6
|
+
import { withAssetHash } from './asset-hash.js';
|
|
6
7
|
|
|
7
8
|
// Local attribute escaper. Matches ssr.js's escapeAttr (the source
|
|
8
9
|
// of truth for HTML attribute escaping in this package). Kept inline
|
|
@@ -53,7 +54,7 @@ let _basePath = '';
|
|
|
53
54
|
*/
|
|
54
55
|
export async function setBasePath(basePath) {
|
|
55
56
|
_basePath = basePath || '';
|
|
56
|
-
_importMapHash = await digestHex('SHA-256', JSON.stringify(buildImportMap()));
|
|
57
|
+
_importMapHash = await digestHex('SHA-256', JSON.stringify(buildImportMap({ fingerprint: false })));
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
/**
|
|
@@ -87,7 +88,7 @@ let _vendorIntegrity = {};
|
|
|
87
88
|
export async function setVendorEntries(entries, integrity) {
|
|
88
89
|
_extraEntries = entries;
|
|
89
90
|
_vendorIntegrity = integrity || {};
|
|
90
|
-
_importMapHash = await digestHex('SHA-256', JSON.stringify(buildImportMap()));
|
|
91
|
+
_importMapHash = await digestHex('SHA-256', JSON.stringify(buildImportMap({ fingerprint: false })));
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
/**
|
|
@@ -228,7 +229,7 @@ let _coreEntries = {
|
|
|
228
229
|
*/
|
|
229
230
|
export async function setCoreInstall(coreDir, distMode) {
|
|
230
231
|
_coreEntries = buildCoreEntries(coreDir, !!distMode);
|
|
231
|
-
_importMapHash = await digestHex('SHA-256', JSON.stringify(buildImportMap()));
|
|
232
|
+
_importMapHash = await digestHex('SHA-256', JSON.stringify(buildImportMap({ fingerprint: false })));
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
/**
|
|
@@ -319,7 +320,21 @@ export function buildCoreEntries(coreDir, distMode) {
|
|
|
319
320
|
return out;
|
|
320
321
|
}
|
|
321
322
|
|
|
322
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Build the import map object.
|
|
325
|
+
*
|
|
326
|
+
* @param {{ fingerprint?: boolean }} [opts] When `fingerprint` is true (the
|
|
327
|
+
* default, used by the SERVED map in `importMapTag`), same-origin targets
|
|
328
|
+
* get a `?v=<content-hash>` suffix for immutable caching (issue #243); a
|
|
329
|
+
* cross-origin CDN target is left untouched (`withAssetHash` skips it, so
|
|
330
|
+
* its SRI key is unchanged). When false (used by the internal
|
|
331
|
+
* `importMapHash()` computation), no `?v` is appended, so the published
|
|
332
|
+
* build id stays a STABLE deploy fingerprint independent of per-file
|
|
333
|
+
* content hashes (the build id only needs to be stable within a deploy).
|
|
334
|
+
* In dev, `withAssetHash` is a no-op regardless, so both forms are equal.
|
|
335
|
+
*/
|
|
336
|
+
export function buildImportMap(opts = {}) {
|
|
337
|
+
const fingerprint = opts.fingerprint !== false;
|
|
323
338
|
const merged = {
|
|
324
339
|
..._coreEntries,
|
|
325
340
|
..._extraEntries,
|
|
@@ -333,11 +348,18 @@ export function buildImportMap() {
|
|
|
333
348
|
/** @type {Record<string, string>} */
|
|
334
349
|
const imports = {};
|
|
335
350
|
// Prefix every same-origin absolute target with the sub-path base
|
|
336
|
-
// (issue #256)
|
|
337
|
-
//
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
|
|
351
|
+
// (issue #256), THEN (issue #243) append `?v=<content-hash>` to a
|
|
352
|
+
// same-origin target for immutable caching. The order is basePath then
|
|
353
|
+
// `?v`, so a sub-path app emits `<basePath>/app/foo.js?v=hash`.
|
|
354
|
+
// `withBasePath` is a no-op when the base path is empty and leaves a
|
|
355
|
+
// cross-origin `https://` CDN vendor target untouched; `withAssetHash`
|
|
356
|
+
// is a no-op in dev / when disabled and also leaves a cross-origin target
|
|
357
|
+
// untouched (only the framework's own `/__webjs/*` core + same-origin
|
|
358
|
+
// app/public targets get a `?v`).
|
|
359
|
+
for (const k of Object.keys(merged).sort()) {
|
|
360
|
+
const based = withBasePath(merged[k], _basePath);
|
|
361
|
+
imports[k] = fingerprint ? withAssetHash(based, _basePath) : based;
|
|
362
|
+
}
|
|
341
363
|
|
|
342
364
|
// Emit `integrity` per the importmap-integrity spec (Chrome 132+,
|
|
343
365
|
// Safari 18.4+, Firefox flagged). Browsers without support ignore
|
|
@@ -366,6 +388,43 @@ export function buildImportMap() {
|
|
|
366
388
|
return out;
|
|
367
389
|
}
|
|
368
390
|
|
|
391
|
+
/**
|
|
392
|
+
* Derive the cross-origin vendor CDN origins from the resolved vendor
|
|
393
|
+
* importmap, most-common first (issue #243, auto vendor preconnect). For an
|
|
394
|
+
* UNPINNED app resolving vendors live from a cross-origin CDN (ga.jspm.io,
|
|
395
|
+
* or jsdelivr / unpkg / skypack with `--from`), this returns that origin so
|
|
396
|
+
* the head can warm DNS + TLS + TCP before the importmap resolves. A
|
|
397
|
+
* same-origin PINNED app (vendors served from `/__webjs/vendor/*` on the
|
|
398
|
+
* app's own origin), or an app with no cross-origin vendors, returns `[]`.
|
|
399
|
+
*
|
|
400
|
+
* Derived from the resolved targets (`_extraEntries`, the vendor map), NOT a
|
|
401
|
+
* hardcoded string, so a `--from jsdelivr` app preconnects to jsdelivr. The
|
|
402
|
+
* origins are deduped + sorted by frequency (the primary CDN first) and the
|
|
403
|
+
* list is bounded so a map spanning several CDNs cannot emit an unbounded
|
|
404
|
+
* preconnect set.
|
|
405
|
+
*
|
|
406
|
+
* @param {number} [max] cap on the number of origins returned (default 2)
|
|
407
|
+
* @returns {string[]} e.g. `['https://ga.jspm.io']`
|
|
408
|
+
*/
|
|
409
|
+
export function vendorPreconnectOrigins(max = 2) {
|
|
410
|
+
/** @type {Map<string, number>} */
|
|
411
|
+
const counts = new Map();
|
|
412
|
+
for (const target of Object.values(_extraEntries)) {
|
|
413
|
+
if (typeof target !== 'string') continue;
|
|
414
|
+
// Only an absolute `scheme://host` cross-origin url has an origin to
|
|
415
|
+
// preconnect. A same-origin `/__webjs/vendor/*` (downloaded-pin) target
|
|
416
|
+
// is `/`-rooted and skipped (no cross-origin to warm).
|
|
417
|
+
if (!/^https?:\/\//i.test(target)) continue;
|
|
418
|
+
let origin;
|
|
419
|
+
try { origin = new URL(target).origin; } catch { continue; }
|
|
420
|
+
counts.set(origin, (counts.get(origin) || 0) + 1);
|
|
421
|
+
}
|
|
422
|
+
return [...counts.entries()]
|
|
423
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
424
|
+
.slice(0, Math.max(0, max))
|
|
425
|
+
.map(([origin]) => origin);
|
|
426
|
+
}
|
|
427
|
+
|
|
369
428
|
/**
|
|
370
429
|
* Serialise the import map to an HTML script tag string.
|
|
371
430
|
*
|
package/src/ssr.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { renderToString, isNotFound, isRedirect, lookupModuleUrl, isLazy, cspNonce } from '@webjsdev/core';
|
|
4
|
-
import { importMapTag, vendorIntegrityFor, publishedBuildId, basePath } from './importmap.js';
|
|
4
|
+
import { importMapTag, vendorIntegrityFor, publishedBuildId, basePath, vendorPreconnectOrigins } from './importmap.js';
|
|
5
5
|
import { withBasePath } from './base-path.js';
|
|
6
|
+
import { withAssetHash } from './asset-hash.js';
|
|
6
7
|
import { jsonForScriptTag } from './script-tag-json.js';
|
|
7
8
|
import { readToken, newToken, cookieHeader } from './csrf.js';
|
|
8
9
|
import { transitiveDeps } from './module-graph.js';
|
|
@@ -786,8 +787,14 @@ function wrapHead(opts) {
|
|
|
786
787
|
// (read from importmap.js's module state), the same value the importmap
|
|
787
788
|
// targets were prefixed with, so the boot specifiers and the map agree.
|
|
788
789
|
const bp = basePath();
|
|
790
|
+
// Content-hash asset URLs (issue #243): after base-path-prefixing, append
|
|
791
|
+
// `?v=<hash>` to a same-origin module specifier for immutable caching. A
|
|
792
|
+
// no-op in dev (so the boot script is byte-identical) and for a bare/
|
|
793
|
+
// cross-origin specifier; same compose order as the importmap targets
|
|
794
|
+
// (basePath then `?v`).
|
|
795
|
+
const fp = (u) => withAssetHash(withBasePath(u, bp), bp);
|
|
789
796
|
const imports = opts.moduleUrls
|
|
790
|
-
.map((u) => `import ${jsonForScriptTag(
|
|
797
|
+
.map((u) => `import ${jsonForScriptTag(fp(u))};`)
|
|
791
798
|
.join('\n');
|
|
792
799
|
const rawLazyEntries = opts.lazyComponents && Object.keys(opts.lazyComponents).length
|
|
793
800
|
? opts.lazyComponents
|
|
@@ -795,9 +802,14 @@ function wrapHead(opts) {
|
|
|
795
802
|
// The lazy map's values are same-origin module URLs `observeLazy` will
|
|
796
803
|
// dynamically import, so prefix them with the base path too (no-op when
|
|
797
804
|
// empty).
|
|
798
|
-
|
|
805
|
+
// The lazy map's values are same-origin module URLs `observeLazy` will
|
|
806
|
+
// dynamically import, so base-path-prefix AND content-hash them (#243), the
|
|
807
|
+
// same as the eager boot specifiers. `fp` is a pure no-op in dev and at the
|
|
808
|
+
// root mount with fingerprinting off, so the mapped map equals the raw one
|
|
809
|
+
// byte-for-byte there; only prod fingerprinting / a sub-path mount changes it.
|
|
810
|
+
const lazyEntries = rawLazyEntries
|
|
799
811
|
? Object.fromEntries(
|
|
800
|
-
Object.entries(rawLazyEntries).map(([tag, u]) => [tag,
|
|
812
|
+
Object.entries(rawLazyEntries).map(([tag, u]) => [tag, fp(u)]),
|
|
801
813
|
)
|
|
802
814
|
: rawLazyEntries;
|
|
803
815
|
const lazyBoot = lazyEntries
|
|
@@ -1102,16 +1114,20 @@ function wrapHead(opts) {
|
|
|
1102
1114
|
// decided on the ORIGINAL url, so the integrity lookup still keys on the
|
|
1103
1115
|
// unprefixed map url and a cross-origin CDN url (never prefixed) keeps its
|
|
1104
1116
|
// crossorigin attribute.
|
|
1105
|
-
|
|
1117
|
+
// Content-hash (#243): the href additionally gets a `?v=<hash>` after the
|
|
1118
|
+
// base-path prefix (a no-op in dev / for a cross-origin url), but
|
|
1119
|
+
// `crossorigin` / `integrity` are still decided on the ORIGINAL url, so the
|
|
1120
|
+
// integrity lookup keys on the unprefixed/unhashed map url and a cross-origin
|
|
1121
|
+
// CDN url (never prefixed, never hashed) keeps its crossorigin attribute.
|
|
1106
1122
|
for (const url of opts.moduleUrls) {
|
|
1107
1123
|
linkTags.push(
|
|
1108
|
-
`<link rel="modulepreload" href="${escapeAttr(
|
|
1124
|
+
`<link rel="modulepreload" href="${escapeAttr(fp(url))}"` +
|
|
1109
1125
|
`${preloadCrossOriginAttr(url)}${integrityAttr(url)}${noncePreload}>`,
|
|
1110
1126
|
);
|
|
1111
1127
|
}
|
|
1112
1128
|
for (const url of opts.preloads || []) {
|
|
1113
1129
|
linkTags.push(
|
|
1114
|
-
`<link rel="modulepreload" href="${escapeAttr(
|
|
1130
|
+
`<link rel="modulepreload" href="${escapeAttr(fp(url))}"` +
|
|
1115
1131
|
`${preloadCrossOriginAttr(url)}${integrityAttr(url)}${noncePreload}>`,
|
|
1116
1132
|
);
|
|
1117
1133
|
}
|
|
@@ -1125,6 +1141,58 @@ function wrapHead(opts) {
|
|
|
1125
1141
|
}
|
|
1126
1142
|
}
|
|
1127
1143
|
|
|
1144
|
+
// preconnect / dns-prefetch hints (issue #243). `metadata.preconnect` and
|
|
1145
|
+
// `metadata.dnsPrefetch` each take a URL string, `{ url, crossorigin? }`, or
|
|
1146
|
+
// an array of those. A preconnect warms DNS + TLS + TCP; dns-prefetch only
|
|
1147
|
+
// resolves DNS (no crossorigin). The framework ALSO auto-emits ONE preconnect
|
|
1148
|
+
// to the resolved vendor CDN origin for an unpinned cross-origin app, so the
|
|
1149
|
+
// browser warms that connection before the importmap resolves. The author's
|
|
1150
|
+
// declared origins are tracked so the auto one is not a duplicate.
|
|
1151
|
+
/** @type {Set<string>} the origins the author already declared a preconnect to */
|
|
1152
|
+
const declaredPreconnectOrigins = new Set();
|
|
1153
|
+
/** @param {unknown} hint @returns {{ url: string, crossorigin?: string|boolean } | null} */
|
|
1154
|
+
const normalizeHint = (hint) => {
|
|
1155
|
+
if (typeof hint === 'string') return hint ? { url: hint } : null;
|
|
1156
|
+
if (hint && typeof hint === 'object' && typeof (/** @type {any} */ (hint).url) === 'string') {
|
|
1157
|
+
return /** @type {any} */ (hint);
|
|
1158
|
+
}
|
|
1159
|
+
return null;
|
|
1160
|
+
};
|
|
1161
|
+
/** @param {unknown} value @returns {Array<{ url: string, crossorigin?: string|boolean }>} */
|
|
1162
|
+
const toHints = (value) => {
|
|
1163
|
+
if (value == null) return [];
|
|
1164
|
+
const list = Array.isArray(value) ? value : [value];
|
|
1165
|
+
const out = [];
|
|
1166
|
+
for (const h of list) {
|
|
1167
|
+
const n = normalizeHint(h);
|
|
1168
|
+
if (n) out.push(n);
|
|
1169
|
+
}
|
|
1170
|
+
return out;
|
|
1171
|
+
};
|
|
1172
|
+
/** @param {string|boolean|undefined} co @returns {string} */
|
|
1173
|
+
const crossoriginAttr = (co) => {
|
|
1174
|
+
if (co === undefined || co === false) return '';
|
|
1175
|
+
if (co === true || co === '') return ' crossorigin';
|
|
1176
|
+
return ` crossorigin="${escapeAttr(String(co))}"`;
|
|
1177
|
+
};
|
|
1178
|
+
for (const h of toHints(m.preconnect)) {
|
|
1179
|
+
try { declaredPreconnectOrigins.add(new URL(h.url).origin); } catch { /* relative / opaque; track by raw href instead */ declaredPreconnectOrigins.add(h.url); }
|
|
1180
|
+
linkTags.push(`<link rel="preconnect" href="${escapeAttr(h.url)}"${crossoriginAttr(h.crossorigin)}>`);
|
|
1181
|
+
}
|
|
1182
|
+
for (const h of toHints(m.dnsPrefetch)) {
|
|
1183
|
+
// dns-prefetch never carries crossorigin (it only resolves DNS).
|
|
1184
|
+
linkTags.push(`<link rel="dns-prefetch" href="${escapeAttr(h.url)}">`);
|
|
1185
|
+
}
|
|
1186
|
+
// Auto vendor preconnect: warm the cross-origin vendor CDN connection for an
|
|
1187
|
+
// unpinned app. Deduped against an author-declared preconnect to the same
|
|
1188
|
+
// origin; emits none for a same-origin pinned app or one with no cross-origin
|
|
1189
|
+
// vendors (vendorPreconnectOrigins returns []). crossorigin is required (the
|
|
1190
|
+
// importmap fetches the module as a CORS request).
|
|
1191
|
+
for (const origin of vendorPreconnectOrigins()) {
|
|
1192
|
+
if (declaredPreconnectOrigins.has(origin)) continue;
|
|
1193
|
+
linkTags.push(`<link rel="preconnect" href="${escapeAttr(origin)}" crossorigin>`);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1128
1196
|
// icons: { icon, apple, shortcut, other }. Each entry can be a string
|
|
1129
1197
|
// (URL), an object { url, sizes?, type? }, or an array of those.
|
|
1130
1198
|
// - icon → <link rel="icon">
|
package/src/testing.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type overlay for `@webjsdev/server/testing` (the handle() test harness, #267).
|
|
3
|
+
*
|
|
4
|
+
* The runtime is packages/server/src/testing.js (JSDoc-annotated JavaScript);
|
|
5
|
+
* this overlay exists so a TypeScript app's `import { testRequest, getCsrf }
|
|
6
|
+
* from '@webjsdev/server/testing'` resolves precise types instead of emitting
|
|
7
|
+
* TS7016. The same declarations are re-exported from the package's main
|
|
8
|
+
* `index.d.ts` (the helpers ship from both entry points). Zero runtime cost.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** The `handle(req)` function returned by `createRequestHandler`. */
|
|
12
|
+
export type Handle = (req: Request) => Promise<Response> | Response;
|
|
13
|
+
|
|
14
|
+
/** A `createRequestHandler` return value, or a bare `handle` function. */
|
|
15
|
+
export type AppOrHandle = { handle: Handle; appDir: string } | Handle;
|
|
16
|
+
|
|
17
|
+
/** A CSRF token + cookie + header-name triple minted off the first SSR response. */
|
|
18
|
+
export interface CsrfPair {
|
|
19
|
+
/** The bare token value for the `x-webjs-csrf` request header. */
|
|
20
|
+
token: string;
|
|
21
|
+
/** The header NAME (`x-webjs-csrf`). */
|
|
22
|
+
header: string;
|
|
23
|
+
/** The `name=value` cookie string for the `Cookie` request header. */
|
|
24
|
+
cookie: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Coerce a bare path, full URL string, or pre-built `Request` into a `Request`.
|
|
29
|
+
* A bare path is prefixed with a dummy origin; `init` is merged when provided.
|
|
30
|
+
*/
|
|
31
|
+
export declare function toRequest(input: string | Request, init?: RequestInit): Request;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Fire a request through the real `handle()` pipeline and return the `Response`.
|
|
35
|
+
* The documented one-liner: `await testRequest(app.handle, '/about')`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function testRequest(
|
|
38
|
+
handle: Handle,
|
|
39
|
+
input: string | Request,
|
|
40
|
+
init?: RequestInit,
|
|
41
|
+
): Promise<Response>;
|
|
42
|
+
|
|
43
|
+
/** Read the `webjs_csrf` cookie value off a response's `Set-Cookie` header(s). */
|
|
44
|
+
export declare function readCsrfCookie(res: Response): string | null;
|
|
45
|
+
|
|
46
|
+
/** Collect all `Set-Cookie` header values off a response. */
|
|
47
|
+
export declare function getSetCookies(res: Response): string[];
|
|
48
|
+
|
|
49
|
+
/** Reduce raw `Set-Cookie` strings to a single `name=value; name2=value2` `Cookie` header value. */
|
|
50
|
+
export declare function cookiesToHeader(setCookies: string[]): string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Mint a `{ token, header, cookie }` CSRF triple off the first SSR response.
|
|
54
|
+
* `path` is the GET path used to issue the cookie (default `/`).
|
|
55
|
+
*/
|
|
56
|
+
export declare function getCsrf(handle: Handle, path?: string): Promise<CsrfPair>;
|
|
57
|
+
|
|
58
|
+
/** Merge an extra cookie string into a `RequestInit`'s `Cookie` header. */
|
|
59
|
+
export declare function withCookies(init: RequestInit | undefined, cookieValue: string): RequestInit;
|
|
60
|
+
|
|
61
|
+
/** Merge a session cookie string into a `RequestInit`'s `Cookie` header. */
|
|
62
|
+
export declare function withSessionCookie(init: RequestInit | undefined, sessionCookie: string): RequestInit;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Drive the REAL credentials login (`POST /api/auth/signin/credentials` by
|
|
66
|
+
* default) and capture the genuine signed session `Set-Cookie`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function loginAndGetCookies(
|
|
69
|
+
handle: Handle,
|
|
70
|
+
credentials: { email: string; password: string },
|
|
71
|
+
opts?: {
|
|
72
|
+
loginPath?: string;
|
|
73
|
+
method?: string;
|
|
74
|
+
body?: BodyInit;
|
|
75
|
+
contentType?: string;
|
|
76
|
+
expectStatuses?: number[];
|
|
77
|
+
},
|
|
78
|
+
): Promise<{ cookies: string; setCookies: string[]; response: Response }>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Compute the `/__webjs/action/<hash>/<fn>` RPC endpoint path for an action,
|
|
82
|
+
* addressing it the same way the generated client stub does.
|
|
83
|
+
*/
|
|
84
|
+
export declare function actionEndpoint(
|
|
85
|
+
appDir: string,
|
|
86
|
+
serverFilePath: string,
|
|
87
|
+
fnName: string,
|
|
88
|
+
): Promise<string>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Round-trip an action through its REAL RPC endpoint (serializer + CSRF + prod
|
|
92
|
+
* error sanitization) and return the parsed return value.
|
|
93
|
+
*/
|
|
94
|
+
export declare function invokeActionForTest<T = unknown>(
|
|
95
|
+
app: AppOrHandle,
|
|
96
|
+
serverFilePath: string,
|
|
97
|
+
fnName: string,
|
|
98
|
+
args?: unknown[],
|
|
99
|
+
opts?: {
|
|
100
|
+
csrf?: CsrfPair;
|
|
101
|
+
appDir?: string;
|
|
102
|
+
extraCookies?: string;
|
|
103
|
+
throwOnError?: boolean;
|
|
104
|
+
},
|
|
105
|
+
): Promise<T>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Lower-level variant of `invokeActionForTest` that returns the raw `Response`
|
|
109
|
+
* (never throws on a non-2xx), so a test can assert the status directly.
|
|
110
|
+
*/
|
|
111
|
+
export declare function rawActionRequest(
|
|
112
|
+
app: AppOrHandle,
|
|
113
|
+
serverFilePath: string,
|
|
114
|
+
fnName: string,
|
|
115
|
+
args?: unknown[],
|
|
116
|
+
opts?: {
|
|
117
|
+
csrf?: CsrfPair | null;
|
|
118
|
+
omitCsrf?: boolean;
|
|
119
|
+
appDir?: string;
|
|
120
|
+
extraCookies?: string;
|
|
121
|
+
contentType?: string;
|
|
122
|
+
},
|
|
123
|
+
): Promise<Response>;
|