@ripple-ts/vite-plugin 0.3.34 → 0.3.35
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 +24 -0
- package/package.json +4 -4
- package/src/index.js +196 -109
- package/tests/configure-server.test.js +288 -0
- package/types/index.d.ts +199 -201
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @ripple-ts/vite-plugin
|
|
2
2
|
|
|
3
|
+
## 0.3.35
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#966](https://github.com/Ripple-TS/ripple/pull/966)
|
|
8
|
+
[`caf83e3`](https://github.com/Ripple-TS/ripple/commit/caf83e386faa9133df70460f266fc27ab323082b)
|
|
9
|
+
Thanks [@RazinShafayet2007](https://github.com/RazinShafayet2007)! - fix:
|
|
10
|
+
register SSR/API middleware as a pre-hook so it runs before Vite's HTML fallback
|
|
11
|
+
middleware
|
|
12
|
+
|
|
13
|
+
The dev server's `configureServer` hook previously returned a function
|
|
14
|
+
(post-hook), which registered SSR/API middleware after Vite's internal
|
|
15
|
+
middleware stack. Vite's HTML fallback middleware would intercept all non-file
|
|
16
|
+
GET requests first, preventing SSR rendering and API routes from ever executing.
|
|
17
|
+
|
|
18
|
+
Switched to a pre-hook (no return value) so middleware is registered before Vite
|
|
19
|
+
internals. Config loading is deferred to the first request via
|
|
20
|
+
`ensureConfigLoaded()`, which retries on missing config and surfaces load errors
|
|
21
|
+
as dev-server 500 pages instead of silently falling through.
|
|
22
|
+
|
|
23
|
+
- Updated dependencies []:
|
|
24
|
+
- @tsrx/ripple@0.0.17
|
|
25
|
+
- @ripple-ts/adapter@0.3.35
|
|
26
|
+
|
|
3
27
|
## 0.3.34
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Vite plugin for Ripple",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.35",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/index.js",
|
|
9
9
|
"main": "src/index.js",
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
"url": "https://github.com/Ripple-TS/ripple/issues"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@ripple-ts/adapter": "0.3.
|
|
36
|
-
"@tsrx/ripple": "0.0.
|
|
35
|
+
"@ripple-ts/adapter": "0.3.35",
|
|
36
|
+
"@tsrx/ripple": "0.0.17"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^24.3.0",
|
|
40
40
|
"type-fest": "^5.6.0",
|
|
41
41
|
"vite": "^8.0.0",
|
|
42
|
-
"ripple": "0.3.
|
|
42
|
+
"ripple": "0.3.35"
|
|
43
43
|
},
|
|
44
44
|
"publishConfig": {
|
|
45
45
|
"access": "public"
|
package/src/index.js
CHANGED
|
@@ -574,138 +574,225 @@ export function ripple(inlineOptions = {}) {
|
|
|
574
574
|
},
|
|
575
575
|
|
|
576
576
|
/**
|
|
577
|
-
* Configure the dev server with SSR middleware
|
|
577
|
+
* Configure the dev server with SSR middleware.
|
|
578
|
+
*
|
|
579
|
+
* Uses a pre-hook (no return value) so that Ripple's SSR/API
|
|
580
|
+
* middleware is registered BEFORE Vite's internal middlewares.
|
|
581
|
+
* Route-owning middleware must run before Vite's HTML fallback
|
|
582
|
+
* middleware, which otherwise intercepts non-file GET requests
|
|
583
|
+
* and serves index.html.
|
|
584
|
+
*
|
|
585
|
+
* Config loading is deferred until the first incoming request so
|
|
586
|
+
* that `vite.ssrLoadModule` is guaranteed to be fully initialised.
|
|
587
|
+
*
|
|
578
588
|
* @param {ViteDevServer} vite
|
|
579
589
|
*/
|
|
580
590
|
configureServer(vite) {
|
|
581
|
-
//
|
|
582
|
-
|
|
583
|
-
|
|
591
|
+
// Deferred config initialisation — resolved on first request
|
|
592
|
+
// that finds a ripple.config.ts. The promise is cleared after
|
|
593
|
+
// every attempt so that "config missing" is never cached
|
|
594
|
+
// permanently (the user may create the file while the dev
|
|
595
|
+
// server is running).
|
|
596
|
+
/** @type {Promise<void> | null} */
|
|
597
|
+
let initPromise = null;
|
|
598
|
+
/** @type {number} */
|
|
599
|
+
let lastConfigErrorMtimeMs = 0;
|
|
584
600
|
|
|
585
|
-
|
|
586
|
-
|
|
601
|
+
/**
|
|
602
|
+
* Ensure ripple.config.ts has been loaded and the router is
|
|
603
|
+
* ready. Safe to call on every request — a successful load
|
|
604
|
+
* (even with no routes) is short-circuited, a missing config
|
|
605
|
+
* file is retried on the next request, and load errors are
|
|
606
|
+
* only retried when the file has been modified.
|
|
607
|
+
*/
|
|
608
|
+
async function ensureConfigLoaded() {
|
|
609
|
+
// Config and router are already loaded.
|
|
610
|
+
if (rippleConfig && router) return;
|
|
587
611
|
|
|
588
|
-
|
|
612
|
+
if (initPromise) {
|
|
613
|
+
await initPromise;
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const configPath = getRippleConfigPath(root);
|
|
618
|
+
|
|
619
|
+
// Config file doesn't exist (yet). Don't cache this — the
|
|
620
|
+
// user may create it while the dev server is running.
|
|
621
|
+
if (!rippleConfigExists(root)) return;
|
|
622
|
+
|
|
623
|
+
// After a load error, only retry if the file has been
|
|
624
|
+
// modified since the last failure. This avoids per-request
|
|
625
|
+
// log spam while instantly picking up fixes.
|
|
626
|
+
if (lastConfigErrorMtimeMs) {
|
|
627
|
+
try {
|
|
628
|
+
const stat = fs.statSync(configPath);
|
|
629
|
+
if (stat.mtimeMs <= lastConfigErrorMtimeMs) return;
|
|
630
|
+
} catch {
|
|
589
631
|
return;
|
|
590
632
|
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (!initPromise) {
|
|
636
|
+
// Snapshot mtime before loading into a local variable.
|
|
637
|
+
// Only promoted to lastConfigErrorMtimeMs if the load
|
|
638
|
+
// actually fails — this prevents concurrent requests
|
|
639
|
+
// during a normal first load from seeing a non-zero
|
|
640
|
+
// lastConfigErrorMtimeMs and short-circuiting above.
|
|
641
|
+
let preLoadMtimeMs;
|
|
642
|
+
try {
|
|
643
|
+
preLoadMtimeMs = fs.statSync(configPath).mtimeMs;
|
|
644
|
+
} catch {
|
|
645
|
+
preLoadMtimeMs = Date.now();
|
|
646
|
+
}
|
|
591
647
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
648
|
+
initPromise = (async () => {
|
|
649
|
+
const nextConfig = await loadRippleConfig(root, { vite });
|
|
650
|
+
|
|
651
|
+
let nextRouter = null;
|
|
652
|
+
if (has_route_config(nextConfig)) {
|
|
653
|
+
nextRouter = createRouter(nextConfig.router.routes);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
rippleConfig = nextConfig;
|
|
657
|
+
router = nextRouter;
|
|
658
|
+
|
|
659
|
+
if (nextRouter) {
|
|
660
|
+
console.log(
|
|
661
|
+
`[@ripple-ts/vite-plugin] Loaded ${nextConfig.router.routes.length} routes from ripple.config.ts`,
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
})()
|
|
665
|
+
.catch((error) => {
|
|
666
|
+
// Record pre-load mtime so retries only happen
|
|
667
|
+
// when the file has been modified.
|
|
668
|
+
lastConfigErrorMtimeMs = preLoadMtimeMs;
|
|
669
|
+
throw error;
|
|
670
|
+
})
|
|
671
|
+
.finally(() => {
|
|
672
|
+
initPromise = null;
|
|
673
|
+
});
|
|
600
674
|
}
|
|
601
675
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
676
|
+
await initPromise;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Pre-hook: register middleware directly without returning a
|
|
680
|
+
// function, so it is inserted BEFORE Vite's built-in stack.
|
|
681
|
+
vite.middlewares.use(function rippleDevMiddleware(req, res, next) {
|
|
682
|
+
// Handle async logic in an IIFE
|
|
683
|
+
(async () => {
|
|
684
|
+
// Lazy-load ripple.config.ts. This is deferred to the
|
|
685
|
+
// first request because vite.ssrLoadModule may not be
|
|
686
|
+
// fully initialised when configureServer runs.
|
|
687
|
+
try {
|
|
688
|
+
await ensureConfigLoaded();
|
|
689
|
+
} catch (error) {
|
|
690
|
+
// Log but do NOT return a 500 — falling through to
|
|
691
|
+
// next() lets Vite continue serving its own internal
|
|
692
|
+
// requests (HMR, CSS, JS modules, etc.). A broken
|
|
693
|
+
// ripple.config.ts should not kill the entire dev
|
|
694
|
+
// server. The error is retried on the next request
|
|
695
|
+
// because ensureConfigLoaded clears initPromise.
|
|
696
|
+
vite.ssrFixStacktrace(/** @type {Error} */ (error));
|
|
697
|
+
console.error('[@ripple-ts/vite-plugin] Failed to load ripple.config.ts:', error);
|
|
698
|
+
next();
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Skip if no router
|
|
703
|
+
if (!router || !rippleConfig) {
|
|
704
|
+
next();
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
|
709
|
+
const method = req.method || 'GET';
|
|
710
|
+
|
|
711
|
+
// Handle RPC requests for #server blocks
|
|
712
|
+
if (is_rpc_request(url.pathname)) {
|
|
713
|
+
await handleRpcRequest(req, res, vite, rippleConfig.server.trustProxy, rippleConfig);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Match route
|
|
718
|
+
const match = router.match(method, url.pathname);
|
|
719
|
+
|
|
720
|
+
if (!match) {
|
|
721
|
+
next();
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
try {
|
|
726
|
+
// Reload config to get fresh routes (for HMR)
|
|
727
|
+
const previousRoutes = rippleConfig.router.routes;
|
|
728
|
+
const freshConfig = await loadRippleConfig(root, { vite });
|
|
729
|
+
if (freshConfig) {
|
|
730
|
+
rippleConfig = freshConfig;
|
|
610
731
|
}
|
|
611
732
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
if (is_rpc_request(url.pathname)) {
|
|
617
|
-
await handleRpcRequest(
|
|
618
|
-
req,
|
|
619
|
-
res,
|
|
620
|
-
vite,
|
|
621
|
-
rippleConfig.server.trustProxy,
|
|
622
|
-
rippleConfig,
|
|
733
|
+
// Check if routes have changed
|
|
734
|
+
if (JSON.stringify(previousRoutes) !== JSON.stringify(rippleConfig.router.routes)) {
|
|
735
|
+
console.log(
|
|
736
|
+
`[@ripple-ts/vite-plugin] Detected route changes. Re-loading ${rippleConfig.router.routes.length} routes from ripple.config.ts`,
|
|
623
737
|
);
|
|
624
|
-
return;
|
|
625
738
|
}
|
|
626
739
|
|
|
627
|
-
|
|
628
|
-
const match = router.match(method, url.pathname);
|
|
740
|
+
router = createRouter(rippleConfig.router.routes);
|
|
629
741
|
|
|
630
|
-
|
|
742
|
+
// Re-match with fresh router
|
|
743
|
+
const freshMatch = router.match(method, url.pathname);
|
|
744
|
+
if (!freshMatch) {
|
|
631
745
|
next();
|
|
632
746
|
return;
|
|
633
747
|
}
|
|
634
748
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
// Re-match with fresh router
|
|
653
|
-
const freshMatch = router.match(method, url.pathname);
|
|
654
|
-
if (!freshMatch) {
|
|
655
|
-
next();
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// Create context
|
|
660
|
-
const request = nodeRequestToWebRequest(req);
|
|
661
|
-
const context = createContext(request, freshMatch.params);
|
|
662
|
-
|
|
663
|
-
const globalMiddlewares = rippleConfig.middlewares;
|
|
664
|
-
|
|
665
|
-
let response;
|
|
666
|
-
|
|
667
|
-
if (freshMatch.route.type === 'render') {
|
|
668
|
-
// Handle RenderRoute with global middlewares
|
|
669
|
-
response = await runMiddlewareChain(
|
|
670
|
-
context,
|
|
671
|
-
globalMiddlewares,
|
|
672
|
-
freshMatch.route.before || [],
|
|
673
|
-
async () =>
|
|
674
|
-
handleRenderRoute(
|
|
675
|
-
/** @type {RenderRoute} */ (freshMatch.route),
|
|
676
|
-
context,
|
|
677
|
-
vite,
|
|
678
|
-
),
|
|
679
|
-
[],
|
|
680
|
-
);
|
|
681
|
-
} else {
|
|
682
|
-
// Handle ServerRoute
|
|
683
|
-
response = await handleServerRoute(freshMatch.route, context, globalMiddlewares);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// Send response
|
|
687
|
-
await sendWebResponse(res, response);
|
|
688
|
-
} catch (error) {
|
|
689
|
-
console.error('[@ripple-ts/vite-plugin] Request error:', error);
|
|
690
|
-
vite.ssrFixStacktrace(/** @type {Error} */ (error));
|
|
691
|
-
|
|
692
|
-
res.statusCode = 500;
|
|
693
|
-
res.setHeader('Content-Type', 'text/html');
|
|
694
|
-
res.end(
|
|
695
|
-
`<pre style="color: red; background: #1a1a1a; padding: 2rem; margin: 0;">${escapeHtml(
|
|
696
|
-
error instanceof Error ? error.stack || error.message : String(error),
|
|
697
|
-
)}</pre>`,
|
|
749
|
+
// Create context
|
|
750
|
+
const request = nodeRequestToWebRequest(req);
|
|
751
|
+
const context = createContext(request, freshMatch.params);
|
|
752
|
+
|
|
753
|
+
const globalMiddlewares = rippleConfig.middlewares;
|
|
754
|
+
|
|
755
|
+
let response;
|
|
756
|
+
|
|
757
|
+
if (freshMatch.route.type === 'render') {
|
|
758
|
+
// Handle RenderRoute with global middlewares
|
|
759
|
+
response = await runMiddlewareChain(
|
|
760
|
+
context,
|
|
761
|
+
globalMiddlewares,
|
|
762
|
+
freshMatch.route.before || [],
|
|
763
|
+
async () =>
|
|
764
|
+
handleRenderRoute(/** @type {RenderRoute} */ (freshMatch.route), context, vite),
|
|
765
|
+
[],
|
|
698
766
|
);
|
|
767
|
+
} else {
|
|
768
|
+
// Handle ServerRoute
|
|
769
|
+
response = await handleServerRoute(freshMatch.route, context, globalMiddlewares);
|
|
699
770
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
}
|
|
706
|
-
|
|
771
|
+
|
|
772
|
+
// Send response
|
|
773
|
+
await sendWebResponse(res, response);
|
|
774
|
+
} catch (error) {
|
|
775
|
+
console.error('[@ripple-ts/vite-plugin] Request error:', error);
|
|
776
|
+
vite.ssrFixStacktrace(/** @type {Error} */ (error));
|
|
777
|
+
|
|
778
|
+
res.statusCode = 500;
|
|
779
|
+
res.setHeader('Content-Type', 'text/html');
|
|
780
|
+
res.end(
|
|
781
|
+
`<pre style="color: red; background: #1a1a1a; padding: 2rem; margin: 0;">${escapeHtml(
|
|
782
|
+
error instanceof Error ? error.stack || error.message : String(error),
|
|
783
|
+
)}</pre>`,
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
})().catch((err) => {
|
|
787
|
+
console.error('[@ripple-ts/vite-plugin] Unhandled middleware error:', err);
|
|
788
|
+
if (!res.headersSent) {
|
|
789
|
+
res.statusCode = 500;
|
|
790
|
+
res.end('Internal Server Error');
|
|
791
|
+
}
|
|
707
792
|
});
|
|
708
|
-
};
|
|
793
|
+
});
|
|
794
|
+
// No return — pre-hook ensures middleware runs before
|
|
795
|
+
// viteHtmlFallbackMiddleware
|
|
709
796
|
},
|
|
710
797
|
|
|
711
798
|
/**
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { ripple } from '@ripple-ts/vite-plugin';
|
|
3
|
+
import { createServer } from 'vite';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Tests for configureServer middleware ordering.
|
|
10
|
+
*
|
|
11
|
+
* Ripple's SSR/API middleware is route-owning middleware. It must be
|
|
12
|
+
* registered as a pre-hook (no return value from configureServer) so
|
|
13
|
+
* it runs before Vite's HTML fallback middleware, which otherwise
|
|
14
|
+
* intercepts non-file GET requests and serves index.html.
|
|
15
|
+
*/
|
|
16
|
+
describe('configureServer middleware ordering', () => {
|
|
17
|
+
/**
|
|
18
|
+
* Get the main ripple plugin from the plugin array.
|
|
19
|
+
* @returns {{ plugin: import('vite').Plugin, plugins: import('vite').Plugin[] }}
|
|
20
|
+
*/
|
|
21
|
+
function getPlugins() {
|
|
22
|
+
const plugins = ripple({ excludeRippleExternalModules: true });
|
|
23
|
+
const plugin = plugins.find((p) => p.name === 'vite-plugin-ripple');
|
|
24
|
+
if (!plugin) throw new Error('vite-plugin-ripple not found in plugin array');
|
|
25
|
+
return { plugin, plugins };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create a mock ViteDevServer with just enough surface for configureServer.
|
|
30
|
+
*/
|
|
31
|
+
function createMockVite() {
|
|
32
|
+
/** @type {Function[]} */
|
|
33
|
+
const registeredMiddlewares = [];
|
|
34
|
+
return {
|
|
35
|
+
middlewares: {
|
|
36
|
+
use: vi.fn((/** @type {Function} */ fn) => {
|
|
37
|
+
registeredMiddlewares.push(fn);
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
registeredMiddlewares,
|
|
41
|
+
ssrLoadModule: vi.fn(),
|
|
42
|
+
ssrFixStacktrace: vi.fn(),
|
|
43
|
+
environments: {},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Call configResolved on the plugin to set up `root` and `config`.
|
|
49
|
+
* @param {import('vite').Plugin} plugin
|
|
50
|
+
*/
|
|
51
|
+
async function initPlugin(plugin) {
|
|
52
|
+
if (typeof plugin.configResolved === 'function') {
|
|
53
|
+
await plugin.configResolved(
|
|
54
|
+
/** @type {any} */ ({
|
|
55
|
+
root: '/nonexistent-test-root',
|
|
56
|
+
command: 'serve',
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// --- Unit tests (mocked Vite) ---
|
|
63
|
+
|
|
64
|
+
it('uses a pre-hook — configureServer does NOT return a function', async () => {
|
|
65
|
+
const { plugin } = getPlugins();
|
|
66
|
+
const mockVite = createMockVite();
|
|
67
|
+
|
|
68
|
+
await initPlugin(plugin);
|
|
69
|
+
|
|
70
|
+
const result = plugin.configureServer(/** @type {any} */ (mockVite));
|
|
71
|
+
|
|
72
|
+
// A post-hook returns a function (or async function).
|
|
73
|
+
// A pre-hook returns undefined (void).
|
|
74
|
+
expect(result).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('registers middleware synchronously inside configureServer', async () => {
|
|
78
|
+
const { plugin } = getPlugins();
|
|
79
|
+
const mockVite = createMockVite();
|
|
80
|
+
|
|
81
|
+
await initPlugin(plugin);
|
|
82
|
+
|
|
83
|
+
plugin.configureServer(/** @type {any} */ (mockVite));
|
|
84
|
+
|
|
85
|
+
expect(mockVite.middlewares.use).toHaveBeenCalledTimes(1);
|
|
86
|
+
expect(mockVite.registeredMiddlewares).toHaveLength(1);
|
|
87
|
+
expect(typeof mockVite.registeredMiddlewares[0]).toBe('function');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('middleware calls next() when no ripple config exists', async () => {
|
|
91
|
+
const { plugin } = getPlugins();
|
|
92
|
+
const mockVite = createMockVite();
|
|
93
|
+
|
|
94
|
+
await initPlugin(plugin);
|
|
95
|
+
|
|
96
|
+
plugin.configureServer(/** @type {any} */ (mockVite));
|
|
97
|
+
|
|
98
|
+
const middleware = mockVite.registeredMiddlewares[0];
|
|
99
|
+
expect(middleware).toBeDefined();
|
|
100
|
+
|
|
101
|
+
/** @type {ReturnType<typeof vi.fn>} */
|
|
102
|
+
let next;
|
|
103
|
+
const nextCalled = new Promise((resolve) => {
|
|
104
|
+
next = vi.fn(() => resolve(undefined));
|
|
105
|
+
|
|
106
|
+
const req = /** @type {any} */ ({
|
|
107
|
+
url: '/api/test',
|
|
108
|
+
method: 'GET',
|
|
109
|
+
headers: { host: 'localhost' },
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const res = /** @type {any} */ ({
|
|
113
|
+
statusCode: 200,
|
|
114
|
+
headersSent: false,
|
|
115
|
+
setHeader: vi.fn(),
|
|
116
|
+
end: vi.fn(),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
middleware(req, res, next);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await nextCalled;
|
|
123
|
+
|
|
124
|
+
// @ts-ignore — next is assigned inside the Promise constructor
|
|
125
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('ripple middleware is registered before a simulated html fallback', async () => {
|
|
129
|
+
const { plugin } = getPlugins();
|
|
130
|
+
/** @type {Function[]} */
|
|
131
|
+
const stack = [];
|
|
132
|
+
|
|
133
|
+
const mockVite = /** @type {any} */ ({
|
|
134
|
+
middlewares: {
|
|
135
|
+
use: vi.fn((/** @type {Function} */ fn) => {
|
|
136
|
+
stack.push(fn);
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
ssrLoadModule: vi.fn(),
|
|
140
|
+
ssrFixStacktrace: vi.fn(),
|
|
141
|
+
environments: {},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await initPlugin(plugin);
|
|
145
|
+
|
|
146
|
+
const returnValue = plugin.configureServer(mockVite);
|
|
147
|
+
|
|
148
|
+
// Our middleware should already be registered (pre-hook)
|
|
149
|
+
expect(stack).toHaveLength(1);
|
|
150
|
+
|
|
151
|
+
// Simulate Vite adding its internal html fallback AFTER configureServer
|
|
152
|
+
const htmlFallback = (
|
|
153
|
+
/** @type {any} */ req,
|
|
154
|
+
/** @type {any} */ res,
|
|
155
|
+
/** @type {any} */ _next,
|
|
156
|
+
) => {
|
|
157
|
+
res.setHeader('Content-Type', 'text/html');
|
|
158
|
+
res.end('<html>fallback</html>');
|
|
159
|
+
};
|
|
160
|
+
stack.push(htmlFallback);
|
|
161
|
+
|
|
162
|
+
// Ripple middleware is at index 0, html fallback is at index 1
|
|
163
|
+
expect(stack[0]).not.toBe(htmlFallback);
|
|
164
|
+
expect(stack[1]).toBe(htmlFallback);
|
|
165
|
+
|
|
166
|
+
if (typeof returnValue === 'function') {
|
|
167
|
+
throw new Error(
|
|
168
|
+
'configureServer returned a function — this is a post-hook pattern ' +
|
|
169
|
+
'which places middleware AFTER viteHtmlFallbackMiddleware. ' +
|
|
170
|
+
'SSR/API routes will be unreachable.',
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// --- Integration tests (real Vite dev server) ---
|
|
176
|
+
|
|
177
|
+
/** @type {import('vite').ViteDevServer | null} */
|
|
178
|
+
let server = null;
|
|
179
|
+
|
|
180
|
+
afterEach(async () => {
|
|
181
|
+
if (server) {
|
|
182
|
+
await server.close();
|
|
183
|
+
server = null;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('middleware is ordered before html fallback in a real Vite server', async () => {
|
|
188
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ripple-vite-test-'));
|
|
189
|
+
fs.writeFileSync(path.join(tmpDir, 'index.html'), '<html><body></body></html>');
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
server = await createServer({
|
|
193
|
+
root: tmpDir,
|
|
194
|
+
configFile: false,
|
|
195
|
+
plugins: [ripple({ excludeRippleExternalModules: true })],
|
|
196
|
+
server: { middlewareMode: true },
|
|
197
|
+
logLevel: 'silent',
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const stack = server.middlewares.stack;
|
|
201
|
+
|
|
202
|
+
const rippleIndex = stack.findIndex((/** @type {any} */ layer) => {
|
|
203
|
+
const name = layer.handle?.name || layer.name || '';
|
|
204
|
+
return name === 'rippleDevMiddleware';
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const htmlFallbackIndex = stack.findIndex((/** @type {any} */ layer) => {
|
|
208
|
+
const name = layer.handle?.name || layer.name || '';
|
|
209
|
+
return name.includes('htmlFallback') || name.includes('HtmlFallback');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Our middleware must exist in the stack
|
|
213
|
+
expect(rippleIndex).toBeGreaterThanOrEqual(0);
|
|
214
|
+
|
|
215
|
+
// If Vite has an HTML fallback, our middleware must come before it
|
|
216
|
+
if (htmlFallbackIndex !== -1) {
|
|
217
|
+
expect(rippleIndex).toBeLessThan(htmlFallbackIndex);
|
|
218
|
+
}
|
|
219
|
+
} finally {
|
|
220
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('non-ripple requests pass through to next middleware', async () => {
|
|
225
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ripple-vite-test-'));
|
|
226
|
+
fs.writeFileSync(path.join(tmpDir, 'index.html'), '<html><body>hello</body></html>');
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
server = await createServer({
|
|
230
|
+
root: tmpDir,
|
|
231
|
+
configFile: false,
|
|
232
|
+
plugins: [ripple({ excludeRippleExternalModules: true })],
|
|
233
|
+
server: { middlewareMode: true },
|
|
234
|
+
logLevel: 'silent',
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const response = await new Promise((resolve) => {
|
|
238
|
+
const req = /** @type {any} */ ({
|
|
239
|
+
url: '/',
|
|
240
|
+
method: 'GET',
|
|
241
|
+
headers: { host: 'localhost', accept: 'text/html' },
|
|
242
|
+
on: () => {},
|
|
243
|
+
removeListener: () => {},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const chunks = [];
|
|
247
|
+
const res = /** @type {any} */ ({
|
|
248
|
+
statusCode: 200,
|
|
249
|
+
headersSent: false,
|
|
250
|
+
_headers: {},
|
|
251
|
+
setHeader(key, val) {
|
|
252
|
+
this._headers[key] = val;
|
|
253
|
+
},
|
|
254
|
+
getHeader(key) {
|
|
255
|
+
return this._headers[key];
|
|
256
|
+
},
|
|
257
|
+
writeHead(status, headers) {
|
|
258
|
+
this.statusCode = status;
|
|
259
|
+
if (headers) Object.assign(this._headers, headers);
|
|
260
|
+
},
|
|
261
|
+
write(chunk) {
|
|
262
|
+
chunks.push(chunk);
|
|
263
|
+
},
|
|
264
|
+
end(chunk) {
|
|
265
|
+
if (chunk) chunks.push(chunk);
|
|
266
|
+
resolve({
|
|
267
|
+
statusCode: this.statusCode,
|
|
268
|
+
headers: this._headers,
|
|
269
|
+
body: Buffer.concat(
|
|
270
|
+
chunks.map((c) => (typeof c === 'string' ? Buffer.from(c) : c)),
|
|
271
|
+
).toString(),
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
on: () => {},
|
|
275
|
+
removeListener: () => {},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
server.middlewares.handle(req, res);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Ripple middleware passed through and Vite handled the request
|
|
282
|
+
expect(response.statusCode).toBe(200);
|
|
283
|
+
expect(response.body).toContain('hello');
|
|
284
|
+
} finally {
|
|
285
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
});
|
package/types/index.d.ts
CHANGED
|
@@ -1,212 +1,210 @@
|
|
|
1
1
|
import type { Plugin, BuildEnvironmentOptions, ViteDevServer } from 'vite';
|
|
2
2
|
import type { RuntimePrimitives } from '@ripple-ts/adapter';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
build
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
serve: AdapterServeFunction;
|
|
131
|
-
/**
|
|
132
|
-
* Platform-specific runtime primitives provided by the adapter.
|
|
133
|
-
*
|
|
134
|
-
* These allow the server runtime to operate without depending
|
|
135
|
-
* on Node.js-specific APIs like `node:crypto` or `node:async_hooks`.
|
|
136
|
-
*
|
|
137
|
-
* Required for production builds. In development, the vite plugin
|
|
138
|
-
* falls back to Node.js defaults if not provided.
|
|
139
|
-
*/
|
|
140
|
-
runtime: RuntimePrimitives;
|
|
141
|
-
};
|
|
142
|
-
router?: {
|
|
143
|
-
routes: Route[];
|
|
144
|
-
};
|
|
145
|
-
/** Global middlewares applied to all routes */
|
|
146
|
-
middlewares?: Middleware[];
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Plugin exports
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
export function ripple(options?: RipplePluginOptions): Plugin[];
|
|
9
|
+
export function defineConfig(options: RippleConfigOptions): RippleConfigOptions;
|
|
10
|
+
export function resolveRippleConfig(
|
|
11
|
+
raw: RippleConfigOptions,
|
|
12
|
+
options?: { requireAdapter?: boolean },
|
|
13
|
+
): ResolvedRippleConfig;
|
|
14
|
+
export function getRippleConfigPath(projectRoot: string): string;
|
|
15
|
+
export function rippleConfigExists(projectRoot: string): boolean;
|
|
16
|
+
export function loadRippleConfig(
|
|
17
|
+
projectRoot: string,
|
|
18
|
+
options?: { vite?: ViteDevServer; requireAdapter?: boolean },
|
|
19
|
+
): Promise<ResolvedRippleConfig>;
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Route classes
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
export class RenderRoute {
|
|
26
|
+
readonly type: 'render';
|
|
27
|
+
path: string;
|
|
28
|
+
entry: string;
|
|
29
|
+
layout?: string;
|
|
30
|
+
before: Middleware[];
|
|
31
|
+
constructor(options: RenderRouteOptions);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class ServerRoute {
|
|
35
|
+
readonly type: 'server';
|
|
36
|
+
path: string;
|
|
37
|
+
methods: string[];
|
|
38
|
+
handler: RouteHandler;
|
|
39
|
+
before: Middleware[];
|
|
40
|
+
after: Middleware[];
|
|
41
|
+
constructor(options: ServerRouteOptions);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type Route = RenderRoute | ServerRoute;
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Route options
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
export interface RenderRouteOptions {
|
|
51
|
+
/** URL path pattern (e.g., '/', '/posts/:id', '/docs/*slug') */
|
|
52
|
+
path: string;
|
|
53
|
+
/** Path to the Ripple component entry file */
|
|
54
|
+
entry: string;
|
|
55
|
+
/** Path to the layout component (wraps the entry) */
|
|
56
|
+
layout?: string;
|
|
57
|
+
/** Middleware to run before rendering */
|
|
58
|
+
before?: Middleware[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ServerRouteOptions {
|
|
62
|
+
/** URL path pattern (e.g., '/api/hello', '/api/posts/:id') */
|
|
63
|
+
path: string;
|
|
64
|
+
/** HTTP methods to handle (default: ['GET']) */
|
|
65
|
+
methods?: string[];
|
|
66
|
+
/** Request handler that returns a Response */
|
|
67
|
+
handler: RouteHandler;
|
|
68
|
+
/** Middleware to run before the handler */
|
|
69
|
+
before?: Middleware[];
|
|
70
|
+
/** Middleware to run after the handler */
|
|
71
|
+
after?: Middleware[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// Context and middleware
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
export interface Context {
|
|
79
|
+
/** The incoming Request object */
|
|
80
|
+
request: Request;
|
|
81
|
+
/** URL parameters extracted from the route pattern */
|
|
82
|
+
params: Record<string, string>;
|
|
83
|
+
/** Parsed URL object */
|
|
84
|
+
url: URL;
|
|
85
|
+
/** Shared state for passing data between middlewares */
|
|
86
|
+
state: Map<string, unknown>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type NextFunction = () => Promise<Response>;
|
|
90
|
+
export type Middleware = (context: Context, next: NextFunction) => Response | Promise<Response>;
|
|
91
|
+
export type RouteHandler = (context: Context) => Response | Promise<Response>;
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Configuration
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
export interface RipplePluginOptions {
|
|
98
|
+
excludeRippleExternalModules?: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface CompatFactoryConfig {
|
|
102
|
+
/** Module specifier that exports the compat factory */
|
|
103
|
+
from: string;
|
|
104
|
+
/** Named export to call. Omit to use the module's default export. */
|
|
105
|
+
factory?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface CompatFactory<T = unknown> {
|
|
109
|
+
(): T;
|
|
110
|
+
__ripple_compat__: CompatFactoryConfig;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface CompatEntryValue {
|
|
114
|
+
__ripple_compat__: CompatFactoryConfig;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type CompatConfigEntry = CompatFactoryConfig | CompatFactory | CompatEntryValue;
|
|
118
|
+
|
|
119
|
+
export type CompatConfig = Record<string, CompatConfigEntry>;
|
|
120
|
+
|
|
121
|
+
export interface RippleConfigOptions {
|
|
122
|
+
build?: {
|
|
123
|
+
/** Output directory for the production build. @default 'dist' */
|
|
124
|
+
outDir?: string;
|
|
125
|
+
minify?: boolean;
|
|
126
|
+
target?: BuildEnvironmentOptions['target'];
|
|
127
|
+
};
|
|
128
|
+
adapter?: {
|
|
129
|
+
serve: AdapterServeFunction;
|
|
147
130
|
/**
|
|
148
|
-
*
|
|
131
|
+
* Platform-specific runtime primitives provided by the adapter.
|
|
149
132
|
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
133
|
+
* These allow the server runtime to operate without depending
|
|
134
|
+
* on Node.js-specific APIs like `node:crypto` or `node:async_hooks`.
|
|
152
135
|
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
136
|
+
* Required for production builds. In development, the vite plugin
|
|
137
|
+
* falls back to Node.js defaults if not provided.
|
|
155
138
|
*/
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
* when deriving the request origin (protocol + host).
|
|
164
|
-
*
|
|
165
|
-
* Enable this only when the application is behind a trusted reverse proxy
|
|
166
|
-
* (e.g., nginx, Cloudflare, AWS ALB). When `false` (the default), the
|
|
167
|
-
* protocol is inferred from the socket and the host from the `Host` header.
|
|
168
|
-
*
|
|
169
|
-
* @default false
|
|
170
|
-
*/
|
|
171
|
-
trustProxy?: boolean;
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
139
|
+
runtime: RuntimePrimitives;
|
|
140
|
+
};
|
|
141
|
+
router?: {
|
|
142
|
+
routes: Route[];
|
|
143
|
+
};
|
|
144
|
+
/** Global middlewares applied to all routes */
|
|
145
|
+
middlewares?: Middleware[];
|
|
175
146
|
/**
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
147
|
+
* Client-side TSX compat integrations keyed by kind, e.g. `react` for `<tsx:react>`.
|
|
148
|
+
*
|
|
149
|
+
* You can either pass a descriptor object or import a compat factory directly,
|
|
150
|
+
* as long as that factory export carries Ripple compat metadata.
|
|
151
|
+
*
|
|
152
|
+
* These are compiled into a browser-side compat registry by the Vite plugin,
|
|
153
|
+
* allowing `mount()` / `hydrate()` to pick them up automatically.
|
|
179
154
|
*/
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
155
|
+
compat?: CompatConfig;
|
|
156
|
+
platform?: {
|
|
157
|
+
env: Record<string, string>;
|
|
158
|
+
};
|
|
159
|
+
server?: {
|
|
160
|
+
/**
|
|
161
|
+
* Whether to trust `X-Forwarded-Proto` and `X-Forwarded-Host` headers
|
|
162
|
+
* when deriving the request origin (protocol + host).
|
|
163
|
+
*
|
|
164
|
+
* Enable this only when the application is behind a trusted reverse proxy
|
|
165
|
+
* (e.g., nginx, Cloudflare, AWS ALB). When `false` (the default), the
|
|
166
|
+
* protocol is inferred from the socket and the host from the `Host` header.
|
|
167
|
+
*
|
|
168
|
+
* @default false
|
|
169
|
+
*/
|
|
170
|
+
trustProxy?: boolean;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Resolved configuration with all defaults applied.
|
|
176
|
+
* Returned by `resolveRippleConfig` and `loadRippleConfig`.
|
|
177
|
+
* Consumers should use this type instead of applying ad-hoc defaults.
|
|
178
|
+
*/
|
|
179
|
+
export interface ResolvedRippleConfig {
|
|
180
|
+
build: {
|
|
181
|
+
/** @default 'dist' */
|
|
182
|
+
outDir: string;
|
|
183
|
+
minify?: boolean;
|
|
184
|
+
target?: BuildEnvironmentOptions['target'];
|
|
185
|
+
};
|
|
186
|
+
adapter?: {
|
|
187
|
+
serve: AdapterServeFunction;
|
|
188
|
+
runtime: RuntimePrimitives;
|
|
189
|
+
};
|
|
190
|
+
router: {
|
|
191
|
+
routes: Route[];
|
|
192
|
+
};
|
|
193
|
+
/** @default [] */
|
|
194
|
+
middlewares: Middleware[];
|
|
195
|
+
/** @default {} */
|
|
196
|
+
compat: Record<string, CompatFactoryConfig>;
|
|
197
|
+
platform: {
|
|
196
198
|
/** @default {} */
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
/** @default false */
|
|
204
|
-
trustProxy: boolean;
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export type AdapterServeFunction = (
|
|
209
|
-
handler: (request: Request, platform?: unknown) => Response | Promise<Response>,
|
|
210
|
-
options?: Record<string, unknown>,
|
|
211
|
-
) => { listen: (port?: number) => unknown; close: () => void };
|
|
199
|
+
env: Record<string, string>;
|
|
200
|
+
};
|
|
201
|
+
server: {
|
|
202
|
+
/** @default false */
|
|
203
|
+
trustProxy: boolean;
|
|
204
|
+
};
|
|
212
205
|
}
|
|
206
|
+
|
|
207
|
+
export type AdapterServeFunction = (
|
|
208
|
+
handler: (request: Request, platform?: unknown) => Response | Promise<Response>,
|
|
209
|
+
options?: Record<string, unknown>,
|
|
210
|
+
) => { listen: (port?: number) => unknown; close: () => void };
|