@reckona/mreact-router 0.0.189 → 0.0.191

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.
Files changed (76) hide show
  1. package/README.md +17 -4
  2. package/bin/mreact-router.js +23 -0
  3. package/dist/adapters/aws-lambda.d.ts +33 -13
  4. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  5. package/dist/adapters/aws-lambda.js +128 -20
  6. package/dist/adapters/aws-lambda.js.map +1 -1
  7. package/dist/adapters/cloudflare.d.ts +18 -0
  8. package/dist/adapters/cloudflare.d.ts.map +1 -1
  9. package/dist/adapters/cloudflare.js.map +1 -1
  10. package/dist/adapters/edge.d.ts +2 -0
  11. package/dist/adapters/edge.d.ts.map +1 -1
  12. package/dist/adapters/edge.js.map +1 -1
  13. package/dist/adapters/node.d.ts +16 -2
  14. package/dist/adapters/node.d.ts.map +1 -1
  15. package/dist/adapters/node.js.map +1 -1
  16. package/dist/build.d.ts +6 -0
  17. package/dist/build.d.ts.map +1 -1
  18. package/dist/build.js +57 -12
  19. package/dist/build.js.map +1 -1
  20. package/dist/cache.d.ts +8 -0
  21. package/dist/cache.d.ts.map +1 -1
  22. package/dist/cache.js +34 -13
  23. package/dist/cache.js.map +1 -1
  24. package/dist/cli-options.d.ts +4 -1
  25. package/dist/cli-options.d.ts.map +1 -1
  26. package/dist/cli-options.js +63 -3
  27. package/dist/cli-options.js.map +1 -1
  28. package/dist/cli.js +12 -0
  29. package/dist/cli.js.map +1 -1
  30. package/dist/client.d.ts +1 -1
  31. package/dist/client.d.ts.map +1 -1
  32. package/dist/client.js +1 -0
  33. package/dist/client.js.map +1 -1
  34. package/dist/config.d.ts +4 -0
  35. package/dist/config.d.ts.map +1 -1
  36. package/dist/config.js +6 -2
  37. package/dist/config.js.map +1 -1
  38. package/dist/index.d.ts +19 -16
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +15 -5
  41. package/dist/index.js.map +1 -1
  42. package/dist/link.d.ts +22 -1
  43. package/dist/link.d.ts.map +1 -1
  44. package/dist/link.js +130 -5
  45. package/dist/link.js.map +1 -1
  46. package/dist/render.d.ts.map +1 -1
  47. package/dist/render.js +514 -517
  48. package/dist/render.js.map +1 -1
  49. package/dist/request.d.ts +10 -0
  50. package/dist/request.d.ts.map +1 -0
  51. package/dist/request.js +6 -0
  52. package/dist/request.js.map +1 -0
  53. package/dist/typed-routes.d.ts +4 -5
  54. package/dist/typed-routes.d.ts.map +1 -1
  55. package/dist/typed-routes.js.map +1 -1
  56. package/dist/vite.d.ts +6 -0
  57. package/dist/vite.d.ts.map +1 -1
  58. package/dist/vite.js +4 -1
  59. package/dist/vite.js.map +1 -1
  60. package/package.json +19 -13
  61. package/src/adapters/aws-lambda.ts +227 -56
  62. package/src/adapters/cloudflare.ts +59 -0
  63. package/src/adapters/edge.ts +14 -0
  64. package/src/adapters/node.ts +44 -2
  65. package/src/build.ts +113 -16
  66. package/src/cache.ts +39 -17
  67. package/src/cli-options.ts +92 -6
  68. package/src/cli.ts +12 -0
  69. package/src/client.ts +2 -1
  70. package/src/config.ts +10 -2
  71. package/src/index.ts +57 -10
  72. package/src/link.ts +205 -10
  73. package/src/render.ts +5 -7
  74. package/src/request.ts +35 -0
  75. package/src/typed-routes.ts +4 -4
  76. package/src/vite.ts +30 -1
package/src/build.ts CHANGED
@@ -117,10 +117,18 @@ const serverArtifactFilesystemConcurrency = 2;
117
117
  type ServerTransformOutput = ReturnType<typeof transform>;
118
118
  type ServerTransformCache = Map<string, Promise<ServerTransformOutput>>;
119
119
 
120
+ export type AwsLambdaGeneratedHandlerPreloadMode =
121
+ | "all"
122
+ | "hot-route-requests"
123
+ | "middleware"
124
+ | "none";
125
+
120
126
  /**
121
127
  * Configures an app-router production build and its deployment targets.
122
128
  */
123
129
  export interface BuildAppOptions extends AppRouterProjectOptions {
130
+ awsLambdaPreload?: AwsLambdaGeneratedHandlerPreloadMode | undefined;
131
+ awsLambdaPreloadRoutes?: readonly string[] | undefined;
124
132
  onBuildProgress?: ((event: BuildAppProgressEvent) => void) | undefined;
125
133
  onBuildPhaseTiming?: ((timing: BuildAppPhaseTiming) => void) | undefined;
126
134
  outDir: string;
@@ -261,6 +269,7 @@ export interface AwsLambdaArtifactManifest {
261
269
  files: Array<{ bytes: number; path: string }>;
262
270
  handler: string;
263
271
  runtime: "aws-lambda";
272
+ streamingHandler?: string | undefined;
264
273
  totalBytes: number;
265
274
  version: 1;
266
275
  }
@@ -280,6 +289,8 @@ export interface CloudflarePagesArtifactManifest {
280
289
  * Configures packaging of a built app-router output directory for AWS Lambda.
281
290
  */
282
291
  export interface PackageAwsLambdaArtifactOptions {
292
+ awsLambdaPreload?: AwsLambdaGeneratedHandlerPreloadMode | undefined;
293
+ awsLambdaPreloadRoutes?: readonly string[] | undefined;
283
294
  fromDir: string;
284
295
  handlerEntry?: string | undefined;
285
296
  outDir: string;
@@ -810,7 +821,15 @@ async function buildAppWithResolvedProject(
810
821
 
811
822
  if (shouldTrackBuildPhases === false) {
812
823
  await Promise.all([
813
- ...(shouldBuildAwsLambda ? [writeAwsLambdaHandlerArtifact(options.outDir)] : []),
824
+ ...(shouldBuildAwsLambda
825
+ ? [
826
+ writeAwsLambdaHandlerArtifact(
827
+ options.outDir,
828
+ options.awsLambdaPreload,
829
+ options.awsLambdaPreloadRoutes,
830
+ ),
831
+ ]
832
+ : []),
814
833
  ...(shouldBuildCloudflare
815
834
  ? [
816
835
  writeCloudflareWorkerArtifact({
@@ -825,7 +844,15 @@ async function buildAppWithResolvedProject(
825
844
  } else {
826
845
  await timeBuildPhase(timingSink, progressSink, "adapterArtifacts", async () => {
827
846
  await Promise.all([
828
- ...(shouldBuildAwsLambda ? [writeAwsLambdaHandlerArtifact(options.outDir)] : []),
847
+ ...(shouldBuildAwsLambda
848
+ ? [
849
+ writeAwsLambdaHandlerArtifact(
850
+ options.outDir,
851
+ options.awsLambdaPreload,
852
+ options.awsLambdaPreloadRoutes,
853
+ ),
854
+ ]
855
+ : []),
829
856
  ...(shouldBuildCloudflare
830
857
  ? [
831
858
  writeCloudflareWorkerArtifact({
@@ -888,14 +915,18 @@ async function createIncrementalBuildCacheFingerprint(options: {
888
915
  }))
889
916
  .sort((left, right) => left.file.localeCompare(right.file));
890
917
  const payload = {
891
- appDir: normalizeBuildInputPath(relative(options.project.projectRoot, options.project.routesDir)),
918
+ appDir: normalizeBuildInputPath(
919
+ relative(options.project.projectRoot, options.project.routesDir),
920
+ ),
892
921
  assetBaseUrl: options.project.assetBaseUrl ?? null,
893
922
  clientConsolePureFunctions: options.project.clientConsolePureFunctions ?? [],
894
923
  clientSourceMaps: options.project.clientSourceMaps,
895
924
  define,
896
925
  nodeEnv: process.env.NODE_ENV ?? null,
897
926
  publicAssetBaseUrl: options.project.publicAssetBaseUrl ?? null,
898
- publicDir: normalizeBuildInputPath(relative(options.project.projectRoot, options.project.publicDir)),
927
+ publicDir: normalizeBuildInputPath(
928
+ relative(options.project.projectRoot, options.project.publicDir),
929
+ ),
899
930
  routes,
900
931
  sourceDirs: options.project.allowedSourceDirs
901
932
  .map((directory) => normalizeBuildInputPath(relative(options.project.projectRoot, directory)))
@@ -1009,10 +1040,7 @@ async function readJsonBuildOutput<T>(file: string): Promise<T | undefined> {
1009
1040
  try {
1010
1041
  return JSON.parse(await readFile(file, "utf8")) as T;
1011
1042
  } catch (error) {
1012
- if (
1013
- (isNodeError(error) && error.code === "ENOENT") ||
1014
- error instanceof SyntaxError
1015
- ) {
1043
+ if ((isNodeError(error) && error.code === "ENOENT") || error instanceof SyntaxError) {
1016
1044
  return undefined;
1017
1045
  }
1018
1046
 
@@ -6483,10 +6511,25 @@ async function writeNavigationRuntimeBundle(
6483
6511
  return script;
6484
6512
  }
6485
6513
 
6486
- async function writeAwsLambdaHandlerArtifact(outDir: string): Promise<void> {
6514
+ async function writeAwsLambdaHandlerArtifact(
6515
+ outDir: string,
6516
+ preload?: AwsLambdaGeneratedHandlerPreloadMode,
6517
+ routes?: readonly string[],
6518
+ ): Promise<void> {
6487
6519
  const awsLambdaDir = join(outDir, "aws-lambda");
6488
6520
  await mkdir(awsLambdaDir, { recursive: true });
6489
- await writeFile(join(awsLambdaDir, "mreact-handler.mjs"), awsLambdaHandlerSource(".."));
6521
+ const policy = { mode: preload ?? "middleware", ...(routes === undefined ? {} : { routes }) };
6522
+ await Promise.all([
6523
+ writeFile(
6524
+ join(awsLambdaDir, "mreact-handler.mjs"),
6525
+ awsLambdaHandlerSource("buffered", "..", policy.mode, policy.routes),
6526
+ ),
6527
+ writeFile(
6528
+ join(awsLambdaDir, "mreact-streaming-handler.mjs"),
6529
+ awsLambdaHandlerSource("streaming", "..", policy.mode, policy.routes),
6530
+ ),
6531
+ writeFile(join(awsLambdaDir, "preload-policy.json"), JSON.stringify(policy, null, 2)),
6532
+ ]);
6490
6533
  }
6491
6534
 
6492
6535
  async function writeCloudflareWorkerArtifact(options: {
@@ -6541,7 +6584,17 @@ export async function packageAwsLambdaArtifact(
6541
6584
  outDir: options.outDir,
6542
6585
  });
6543
6586
  if (options.handlerEntry === undefined) {
6544
- await writeFile(join(options.outDir, "mreact-handler.mjs"), awsLambdaHandlerSource(".mreact"));
6587
+ const builtPolicy = await readAwsLambdaGeneratedPreloadPolicy(options.fromDir);
6588
+ const preload = options.awsLambdaPreload ?? builtPolicy.mode;
6589
+ const routes = options.awsLambdaPreloadRoutes ?? builtPolicy.routes;
6590
+ await writeFile(
6591
+ join(options.outDir, "mreact-handler.mjs"),
6592
+ awsLambdaHandlerSource("buffered", ".mreact", preload, routes),
6593
+ );
6594
+ await writeFile(
6595
+ join(options.outDir, "mreact-streaming-handler.mjs"),
6596
+ awsLambdaHandlerSource("streaming", ".mreact", preload, routes),
6597
+ );
6545
6598
  } else {
6546
6599
  await writeAwsLambdaCustomHandlerArtifact({
6547
6600
  entry: options.handlerEntry,
@@ -6559,6 +6612,9 @@ export async function packageAwsLambdaArtifact(
6559
6612
  files,
6560
6613
  handler: "mreact-handler.handler",
6561
6614
  runtime: "aws-lambda",
6615
+ ...(options.handlerEntry === undefined
6616
+ ? { streamingHandler: "mreact-streaming-handler.handler" }
6617
+ : {}),
6562
6618
  totalBytes: files.reduce((total, file) => total + file.bytes, 0),
6563
6619
  version: 1,
6564
6620
  } satisfies AwsLambdaArtifactManifest;
@@ -6788,22 +6844,63 @@ async function collectArtifactFiles(
6788
6844
  );
6789
6845
  }
6790
6846
 
6791
- function awsLambdaHandlerSource(outDirRelativeToHandler: string): string {
6847
+ function awsLambdaHandlerSource(
6848
+ kind: "buffered" | "streaming",
6849
+ outDirRelativeToHandler: string,
6850
+ preload: AwsLambdaGeneratedHandlerPreloadMode = "middleware",
6851
+ routes?: readonly string[],
6852
+ ): string {
6853
+ const createHandler =
6854
+ kind === "streaming"
6855
+ ? "createAwsLambdaStreamingRequestHandler"
6856
+ : "createAwsLambdaRequestHandler";
6792
6857
  return `import { fileURLToPath } from "node:url";
6793
6858
  import { dirname, resolve } from "node:path";
6794
- import { createPreloadedAwsLambdaRequestHandler } from "@reckona/mreact-router/adapters/aws-lambda";
6859
+ import { ${createHandler}, warmAwsLambdaRuntime } from "@reckona/mreact-router/adapters/aws-lambda";
6795
6860
 
6796
6861
  const here = dirname(fileURLToPath(import.meta.url));
6797
-
6798
- export const handler = await createPreloadedAwsLambdaRequestHandler({
6862
+ const options = {
6799
6863
  importPolicy: "generated",
6800
6864
  outDir: resolve(here, ${JSON.stringify(outDirRelativeToHandler)}),
6801
- preload: { mode: "all" },
6865
+ preload: { mode: ${JSON.stringify(preload)}${routes === undefined ? "" : `, routes: ${JSON.stringify(routes)}`} },
6802
6866
  timings: process.env.MREACT_ROUTER_TIMINGS === "1",
6867
+ };
6868
+
6869
+ await warmAwsLambdaRuntime(options);
6870
+ export const handler = ${createHandler}({
6871
+ ...options,
6872
+ preload: { mode: "none" },
6803
6873
  });
6804
6874
  `;
6805
6875
  }
6806
6876
 
6877
+ async function readAwsLambdaGeneratedPreloadPolicy(outDir: string): Promise<{
6878
+ mode: AwsLambdaGeneratedHandlerPreloadMode;
6879
+ routes?: readonly string[] | undefined;
6880
+ }> {
6881
+ const path = join(outDir, "aws-lambda", "preload-policy.json");
6882
+ try {
6883
+ return JSON.parse(await readFile(path, "utf8")) as {
6884
+ mode: AwsLambdaGeneratedHandlerPreloadMode;
6885
+ routes?: readonly string[] | undefined;
6886
+ };
6887
+ } catch (error) {
6888
+ if (isNodeErrorCode(error, "ENOENT")) {
6889
+ return { mode: "middleware" };
6890
+ }
6891
+ throw error;
6892
+ }
6893
+ }
6894
+
6895
+ function isNodeErrorCode(error: unknown, code: string): boolean {
6896
+ return (
6897
+ typeof error === "object" &&
6898
+ error !== null &&
6899
+ "code" in error &&
6900
+ (error as { code?: unknown }).code === code
6901
+ );
6902
+ }
6903
+
6807
6904
  async function validateProductionRoutes(options: {
6808
6905
  clientRouteInferenceCache: ClientRouteInferenceCache;
6809
6906
  files: Record<string, string>;
package/src/cache.ts CHANGED
@@ -58,6 +58,7 @@ interface AppRouterCacheState {
58
58
  interface RouteCacheContext {
59
59
  cache: AppRouterCache;
60
60
  cachePolicy?: RouteCachePolicy | undefined;
61
+ closed?: boolean | undefined;
61
62
  revalidatedPaths: Set<string>;
62
63
  }
63
64
 
@@ -472,15 +473,20 @@ export async function withRouteCacheContext<T>(
472
473
  };
473
474
 
474
475
  return cacheState.storage.run(context, async () => {
475
- const value = await fn();
476
- const cachePolicy = context.cachePolicy;
477
- const revalidatedPaths = Array.from(context.revalidatedPaths);
476
+ try {
477
+ const value = await fn();
478
+ context.closed = true;
479
+ const cachePolicy = context.cachePolicy;
480
+ const revalidatedPaths = Array.from(context.revalidatedPaths);
478
481
 
479
- for (const path of revalidatedPaths) {
480
- await context.cache.deleteByPath(path);
481
- }
482
+ for (const path of revalidatedPaths) {
483
+ await context.cache.deleteByPath(path);
484
+ }
482
485
 
483
- return { cachePolicy, revalidatedPaths, value };
486
+ return { cachePolicy, revalidatedPaths, value };
487
+ } finally {
488
+ context.closed = true;
489
+ }
484
490
  });
485
491
  }
486
492
 
@@ -500,15 +506,17 @@ export function beginRouteCacheContext(cache: AppRouterCache | undefined): {
500
506
  return context.cachePolicy;
501
507
  },
502
508
  async dispose() {
509
+ context.closed = true;
503
510
  const revalidatedPaths = Array.from(context.revalidatedPaths);
504
-
505
- for (const path of revalidatedPaths) {
506
- await context.cache.deleteByPath(path);
507
- }
508
-
509
- const index = cacheState.activeContexts.lastIndexOf(context);
510
- if (index !== -1) {
511
- cacheState.activeContexts.splice(index, 1);
511
+ try {
512
+ for (const path of revalidatedPaths) {
513
+ await context.cache.deleteByPath(path);
514
+ }
515
+ } finally {
516
+ const index = cacheState.activeContexts.lastIndexOf(context);
517
+ if (index !== -1) {
518
+ cacheState.activeContexts.splice(index, 1);
519
+ }
512
520
  }
513
521
 
514
522
  return { revalidatedPaths };
@@ -516,8 +524,22 @@ export function beginRouteCacheContext(cache: AppRouterCache | undefined): {
516
524
  };
517
525
  }
518
526
 
519
- function activeRouteCacheContext(): RouteCacheContext | undefined {
520
- return cacheState.storage.getStore() ?? cacheState.activeContexts.at(-1);
527
+ export function activeRouteCacheContext(): RouteCacheContext | undefined {
528
+ const asyncContext = cacheState.storage.getStore();
529
+
530
+ if (asyncContext !== undefined) {
531
+ return asyncContext.closed === true ? undefined : asyncContext;
532
+ }
533
+
534
+ for (let index = cacheState.activeContexts.length - 1; index >= 0; index -= 1) {
535
+ const context = cacheState.activeContexts[index];
536
+
537
+ if (context?.closed !== true) {
538
+ return context;
539
+ }
540
+ }
541
+
542
+ return undefined;
521
543
  }
522
544
 
523
545
  // Host is excluded from the cache key to prevent attacker-supplied Host
@@ -1,14 +1,18 @@
1
1
  import type { AppRouterLogger, AppRouterLogEvent } from "./logger.js";
2
- import type {
3
- AppRouterBuildTarget,
4
- AppRouterClientSourceMapMode,
2
+ import {
3
+ appRouterBuildTargetMetadata,
4
+ type AppRouterBuildTarget,
5
+ type AppRouterClientSourceMapMode,
5
6
  } from "./config.js";
6
7
  import type { RequestHostPolicy } from "./serve.js";
8
+ import type { AwsLambdaGeneratedHandlerPreloadMode } from "./build.js";
7
9
 
8
10
  export type CliRequestLogMode = "requests";
9
11
  export type CliBuildTarget = AppRouterBuildTarget | "all";
10
12
 
11
13
  export interface ParsedCliArguments {
14
+ awsLambdaPreload?: AwsLambdaGeneratedHandlerPreloadMode | undefined;
15
+ awsLambdaPreloadRoutes?: readonly string[] | undefined;
12
16
  clientSourceMaps?: AppRouterClientSourceMapMode | undefined;
13
17
  command: string;
14
18
  allowedHosts?: readonly string[] | undefined;
@@ -156,6 +160,36 @@ export function parseCliArguments(argv: readonly string[]): ParsedCliArguments {
156
160
  continue;
157
161
  }
158
162
 
163
+ if (value === "--aws-lambda-preload") {
164
+ parsed.awsLambdaPreload = parseCliAwsLambdaPreloadMode(
165
+ readOptionValue(argv, index, "aws-lambda-preload"),
166
+ );
167
+ index += 1;
168
+ continue;
169
+ }
170
+
171
+ if (value.startsWith("--aws-lambda-preload=")) {
172
+ parsed.awsLambdaPreload = parseCliAwsLambdaPreloadMode(
173
+ value.slice("--aws-lambda-preload=".length),
174
+ );
175
+ continue;
176
+ }
177
+
178
+ if (value === "--aws-lambda-preload-routes") {
179
+ parsed.awsLambdaPreloadRoutes = parseCliAwsLambdaPreloadRoutes(
180
+ readOptionValue(argv, index, "aws-lambda-preload-routes"),
181
+ );
182
+ index += 1;
183
+ continue;
184
+ }
185
+
186
+ if (value.startsWith("--aws-lambda-preload-routes=")) {
187
+ parsed.awsLambdaPreloadRoutes = parseCliAwsLambdaPreloadRoutes(
188
+ value.slice("--aws-lambda-preload-routes=".length),
189
+ );
190
+ continue;
191
+ }
192
+
159
193
  if (value.startsWith("--client-source-maps=")) {
160
194
  parsed.clientSourceMaps = parseCliClientSourceMapMode(
161
195
  value.slice("--client-source-maps=".length),
@@ -178,6 +212,11 @@ export function parseCliArguments(argv: readonly string[]): ParsedCliArguments {
178
212
  }
179
213
 
180
214
  export function formatCliHelp(command?: string | undefined): string {
215
+ const defaultTargetLabel = formatBuildTargetDisplayList(
216
+ appRouterBuildTargetMetadata.defaultTargets,
217
+ );
218
+ const allTargetValues = formatBuildTargetValueList(appRouterBuildTargetMetadata.allTargets);
219
+
181
220
  if (command === "build") {
182
221
  return [
183
222
  "Usage: mreact-router build [appDir] [options]",
@@ -186,9 +225,13 @@ export function formatCliHelp(command?: string | undefined): string {
186
225
  "",
187
226
  "Options:",
188
227
  " --target=node|cloudflare|aws-lambda|all",
189
- " Select build artifacts. Defaults to node. aws-lambda writes .mreact/aws-lambda/mreact-handler.mjs and .mreact/server/import-policy.json.",
228
+ ` Select build artifacts. Defaults to ${defaultTargetLabel.toLowerCase()}. all selects ${allTargetValues}. aws-lambda writes .mreact/aws-lambda/mreact-handler.mjs and .mreact/server/import-policy.json.`,
190
229
  " --client-source-maps=none|hidden",
191
230
  " Control production client source map output.",
231
+ " --aws-lambda-preload=middleware|hot-route-requests|all|none",
232
+ " Select generated Lambda initialization preload. Defaults to middleware.",
233
+ " --aws-lambda-preload-routes=/,/login",
234
+ " Select routes for hot-route-requests initialization preload.",
192
235
  " -h, --help",
193
236
  " Show this help message.",
194
237
  "",
@@ -196,6 +239,7 @@ export function formatCliHelp(command?: string | undefined): string {
196
239
  " mreact-router build --target=node",
197
240
  " mreact-router build --target=cloudflare",
198
241
  " mreact-router build --target=aws-lambda",
242
+ " mreact-router build --target=all",
199
243
  ].join("\n");
200
244
  }
201
245
 
@@ -216,6 +260,10 @@ export function formatCliHelp(command?: string | undefined): string {
216
260
  " For aws-lambda only, skip the production node_modules check when a later deploy step installs dependencies into the package directory.",
217
261
  " --handler <entry>",
218
262
  " For aws-lambda only, bundle a custom handler entry into mreact-handler.mjs. App-local extensionless TypeScript imports are bundled; package imports stay external.",
263
+ " --aws-lambda-preload=middleware|hot-route-requests|all|none",
264
+ " Override generated Lambda initialization preload. Omission preserves the build policy; legacy output without policy metadata falls back to middleware.",
265
+ " --aws-lambda-preload-routes=/,/login",
266
+ " Select routes for hot-route-requests initialization preload.",
219
267
  " -h, --help Show this help message.",
220
268
  "",
221
269
  "Examples:",
@@ -272,7 +320,7 @@ export function formatCliHelp(command?: string | undefined): string {
272
320
  "",
273
321
  "Commands:",
274
322
  " dev [appDir] Start the development server.",
275
- " build [appDir] Build Node and Cloudflare artifacts by default.",
323
+ ` build [appDir] Build ${defaultTargetLabel} artifacts by default.`,
276
324
  " build --target=aws-lambda Build Lambda artifacts including generated handler and import policy.",
277
325
  " start [outDir] Serve built Node output.",
278
326
  " package aws-lambda --from .mreact --out .lambda",
@@ -290,6 +338,7 @@ export function formatCliHelp(command?: string | undefined): string {
290
338
  "Examples:",
291
339
  " mreact-router build --target=cloudflare",
292
340
  " mreact-router build --target=aws-lambda",
341
+ " mreact-router build --target=all",
293
342
  " mreact-router package aws-lambda --from .mreact --out .lambda",
294
343
  " mreact-router build --help",
295
344
  ].join("\n");
@@ -302,7 +351,21 @@ export function buildTargetsFromCliTarget(
302
351
  return undefined;
303
352
  }
304
353
 
305
- return target === "all" ? ["node", "cloudflare", "aws-lambda"] : [target];
354
+ return target === "all" ? appRouterBuildTargetMetadata.allTargets : [target];
355
+ }
356
+
357
+ function formatBuildTargetDisplayList(targets: readonly AppRouterBuildTarget[]): string {
358
+ return targets.map(formatBuildTargetDisplayName).join(", ");
359
+ }
360
+
361
+ function formatBuildTargetDisplayName(target: AppRouterBuildTarget): string {
362
+ if (target === "aws-lambda") return "AWS Lambda";
363
+ return target[0]?.toUpperCase() + target.slice(1);
364
+ }
365
+
366
+ function formatBuildTargetValueList(targets: readonly AppRouterBuildTarget[]): string {
367
+ if (targets.length < 2) return targets[0] ?? "";
368
+ return `${targets.slice(0, -1).join(", ")}, and ${targets.at(-1)}`;
306
369
  }
307
370
 
308
371
  export function resolveCliRequestLogMode(
@@ -440,6 +503,29 @@ function parseCliClientSourceMapMode(value: string): AppRouterClientSourceMapMod
440
503
  );
441
504
  }
442
505
 
506
+ function parseCliAwsLambdaPreloadMode(value: string): AwsLambdaGeneratedHandlerPreloadMode {
507
+ if (
508
+ value === "all" ||
509
+ value === "hot-route-requests" ||
510
+ value === "middleware" ||
511
+ value === "none"
512
+ ) {
513
+ return value;
514
+ }
515
+
516
+ throw new Error(
517
+ `Unsupported aws-lambda-preload ${JSON.stringify(value)}. Expected "middleware", "hot-route-requests", "all", or "none".`,
518
+ );
519
+ }
520
+
521
+ function parseCliAwsLambdaPreloadRoutes(value: string): readonly string[] {
522
+ const routes = value.split(",").filter((route) => route.startsWith("/"));
523
+ if (routes.length === 0 || routes.join(",") !== value) {
524
+ throw new Error("aws-lambda-preload-routes must be a comma-separated list of absolute routes.");
525
+ }
526
+ return routes;
527
+ }
528
+
443
529
  function readOptionValue(values: readonly string[], index: number, name: string): string {
444
530
  const value = values[index + 1];
445
531
 
package/src/cli.ts CHANGED
@@ -61,6 +61,12 @@ if (parsed !== undefined) {
61
61
  try {
62
62
  const result = await buildApp({
63
63
  ...loaded.project,
64
+ ...(parsed.awsLambdaPreload === undefined
65
+ ? {}
66
+ : { awsLambdaPreload: parsed.awsLambdaPreload }),
67
+ ...(parsed.awsLambdaPreloadRoutes === undefined
68
+ ? {}
69
+ : { awsLambdaPreloadRoutes: parsed.awsLambdaPreloadRoutes }),
64
70
  ...(parsed.clientSourceMaps === undefined
65
71
  ? {}
66
72
  : { clientSourceMaps: parsed.clientSourceMaps }),
@@ -87,6 +93,12 @@ if (parsed !== undefined) {
87
93
  } else if (command === "package") {
88
94
  if (routeArg === "aws-lambda") {
89
95
  const manifest = await packageAwsLambdaArtifact({
96
+ ...(parsed.awsLambdaPreload === undefined
97
+ ? {}
98
+ : { awsLambdaPreload: parsed.awsLambdaPreload }),
99
+ ...(parsed.awsLambdaPreloadRoutes === undefined
100
+ ? {}
101
+ : { awsLambdaPreloadRoutes: parsed.awsLambdaPreloadRoutes }),
90
102
  fromDir: resolve(parsed.from ?? ".mreact"),
91
103
  ...(parsed.handler === undefined ? {} : { handlerEntry: resolve(parsed.handler) }),
92
104
  outDir: resolve(parsed.out ?? ".lambda"),
package/src/client.ts CHANGED
@@ -103,7 +103,7 @@ export interface ClientRouteInferenceCache {
103
103
  transformedSourceByFile: Map<string, Promise<CachedClientRouteSource>>;
104
104
  }
105
105
 
106
- interface CachedClientRouteSource {
106
+ export interface CachedClientRouteSource {
107
107
  signature: string;
108
108
  source: string;
109
109
  }
@@ -3535,6 +3535,7 @@ function __mreactSyncRouteDataScripts(root, currentRouteId, nextRouteId) {
3535
3535
 
3536
3536
  function __mreactRouteDataScriptIds(...routeIds) {
3537
3537
  const ids = new Set();
3538
+ ids.add("__mreact_auth_session");
3538
3539
 
3539
3540
  for (const routeId of routeIds) {
3540
3541
  if (typeof routeId !== "string" || routeId === "") {
package/src/config.ts CHANGED
@@ -4,6 +4,14 @@ import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
4
4
  * Selects the deployment runtime artifacts emitted by an app-router build.
5
5
  */
6
6
  export type AppRouterBuildTarget = "node" | "cloudflare" | "aws-lambda";
7
+
8
+ export const appRouterBuildTargetMetadata = {
9
+ allTargets: ["node", "cloudflare", "aws-lambda"],
10
+ defaultTargets: ["node"],
11
+ } as const satisfies {
12
+ allTargets: readonly AppRouterBuildTarget[];
13
+ defaultTargets: readonly AppRouterBuildTarget[];
14
+ };
7
15
  /**
8
16
  * Names a browser console method that can be stripped from client bundles.
9
17
  */
@@ -180,7 +188,7 @@ export function resolveBuildTargets(
180
188
  targets: readonly AppRouterBuildTarget[] | undefined,
181
189
  ): readonly AppRouterBuildTarget[] {
182
190
  if (targets === undefined) {
183
- return ["node"];
191
+ return appRouterBuildTargetMetadata.defaultTargets;
184
192
  }
185
193
 
186
194
  const uniqueTargets = [...new Set(targets)];
@@ -190,7 +198,7 @@ export function resolveBuildTargets(
190
198
  }
191
199
 
192
200
  for (const target of uniqueTargets) {
193
- if (target !== "node" && target !== "cloudflare" && target !== "aws-lambda") {
201
+ if (!appRouterBuildTargetMetadata.allTargets.some((supported) => supported === target)) {
194
202
  throw new Error(
195
203
  `Unsupported mreactRouter build target ${JSON.stringify(target)}. Expected "node", "cloudflare", or "aws-lambda".`,
196
204
  );