@reckona/mreact-router 0.0.190 → 0.0.192

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/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>;
@@ -5,11 +5,14 @@ import {
5
5
  type AppRouterClientSourceMapMode,
6
6
  } from "./config.js";
7
7
  import type { RequestHostPolicy } from "./serve.js";
8
+ import type { AwsLambdaGeneratedHandlerPreloadMode } from "./build.js";
8
9
 
9
10
  export type CliRequestLogMode = "requests";
10
11
  export type CliBuildTarget = AppRouterBuildTarget | "all";
11
12
 
12
13
  export interface ParsedCliArguments {
14
+ awsLambdaPreload?: AwsLambdaGeneratedHandlerPreloadMode | undefined;
15
+ awsLambdaPreloadRoutes?: readonly string[] | undefined;
13
16
  clientSourceMaps?: AppRouterClientSourceMapMode | undefined;
14
17
  command: string;
15
18
  allowedHosts?: readonly string[] | undefined;
@@ -157,6 +160,36 @@ export function parseCliArguments(argv: readonly string[]): ParsedCliArguments {
157
160
  continue;
158
161
  }
159
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
+
160
193
  if (value.startsWith("--client-source-maps=")) {
161
194
  parsed.clientSourceMaps = parseCliClientSourceMapMode(
162
195
  value.slice("--client-source-maps=".length),
@@ -195,6 +228,10 @@ export function formatCliHelp(command?: string | undefined): string {
195
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.`,
196
229
  " --client-source-maps=none|hidden",
197
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.",
198
235
  " -h, --help",
199
236
  " Show this help message.",
200
237
  "",
@@ -223,6 +260,10 @@ export function formatCliHelp(command?: string | undefined): string {
223
260
  " For aws-lambda only, skip the production node_modules check when a later deploy step installs dependencies into the package directory.",
224
261
  " --handler <entry>",
225
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.",
226
267
  " -h, --help Show this help message.",
227
268
  "",
228
269
  "Examples:",
@@ -462,6 +503,29 @@ function parseCliClientSourceMapMode(value: string): AppRouterClientSourceMapMod
462
503
  );
463
504
  }
464
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
+
465
529
  function readOptionValue(values: readonly string[], index: number, name: string): string {
466
530
  const value = values[index + 1];
467
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/index.ts CHANGED
@@ -116,6 +116,7 @@ export async function rotateSession<TData>(
116
116
  }
117
117
  export type {
118
118
  AwsLambdaArtifactManifest,
119
+ AwsLambdaGeneratedHandlerPreloadMode,
119
120
  BuildAppPhase,
120
121
  BuildAppPhaseTiming,
121
122
  BuildAppOptions,