qlara 0.1.1 → 0.1.3

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/dist/aws.cjs CHANGED
@@ -464,42 +464,30 @@ var import_esbuild = require("esbuild");
464
464
  var import_node_fs2 = require("fs");
465
465
  var import_node_path2 = require("path");
466
466
  var import_node_url = require("url");
467
+ var import_node_module = require("module");
467
468
  var import_archiver = __toESM(require("archiver"), 1);
468
469
  var import_node_stream = require("stream");
469
470
  var import_meta = {};
470
471
  var BUNDLE_DIR = (0, import_node_path2.join)(".qlara", "bundles");
471
- function getModuleDir() {
472
- if (typeof __dirname !== "undefined") {
473
- return __dirname;
474
- }
475
- return (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
476
- }
477
- function findPackageRoot(startDir) {
478
- let dir = startDir;
479
- for (let i = 0; i < 10; i++) {
480
- const pkgPath = (0, import_node_path2.join)(dir, "package.json");
481
- if ((0, import_node_fs2.existsSync)(pkgPath)) {
482
- try {
483
- const { readFileSync: readFileSync3 } = require("fs");
484
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
485
- if (pkg.name === "qlara") return dir;
486
- } catch {
487
- }
488
- }
489
- const parent = (0, import_node_path2.dirname)(dir);
490
- if (parent === dir) break;
491
- dir = parent;
492
- }
493
- return startDir;
494
- }
495
472
  function resolveEntry(name) {
496
- const moduleDir = getModuleDir();
497
- const sameDirTs = (0, import_node_path2.resolve)(moduleDir, `${name}.ts`);
473
+ const thisDir = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
474
+ const sameDirTs = (0, import_node_path2.resolve)(thisDir, `${name}.ts`);
498
475
  if ((0, import_node_fs2.existsSync)(sameDirTs)) return sameDirTs;
499
- const pkgRoot = findPackageRoot(moduleDir);
500
- const pkgSrcTs = (0, import_node_path2.resolve)(pkgRoot, "src", "provider", "aws", `${name}.ts`);
501
- if ((0, import_node_fs2.existsSync)(pkgSrcTs)) return pkgSrcTs;
502
- return (0, import_node_path2.resolve)(moduleDir, `${name}.js`);
476
+ try {
477
+ const esmRequire = (0, import_node_module.createRequire)(import_meta.url);
478
+ const pkgJsonPath = esmRequire.resolve("qlara/package.json");
479
+ const pkgRoot = (0, import_node_path2.dirname)(pkgJsonPath);
480
+ const srcTs = (0, import_node_path2.join)(pkgRoot, "src", "provider", "aws", `${name}.ts`);
481
+ if ((0, import_node_fs2.existsSync)(srcTs)) return srcTs;
482
+ } catch {
483
+ }
484
+ const fromDist = (0, import_node_path2.resolve)(thisDir, "..", "src", "provider", "aws", `${name}.ts`);
485
+ if ((0, import_node_fs2.existsSync)(fromDist)) return fromDist;
486
+ throw new Error(
487
+ `[qlara] Could not find ${name}.ts Lambda source file. Searched:
488
+ ${sameDirTs}
489
+ ${fromDist}`
490
+ );
503
491
  }
504
492
  async function createZip(filePath, entryName) {
505
493
  return new Promise((resolvePromise, reject) => {
@@ -532,14 +520,15 @@ async function bundleEdgeHandler(config) {
532
520
  define: {
533
521
  __QLARA_BUCKET_NAME__: JSON.stringify(config.bucketName),
534
522
  __QLARA_RENDERER_ARN__: JSON.stringify(config.rendererArn),
535
- __QLARA_REGION__: JSON.stringify(config.region)
523
+ __QLARA_REGION__: JSON.stringify(config.region),
524
+ __QLARA_CACHE_TTL__: String(config.cacheTtl)
536
525
  },
537
526
  // Bundle everything — Lambda@Edge must be self-contained
538
527
  external: []
539
528
  });
540
529
  return createZip(outfile, "edge-handler.js");
541
530
  }
542
- async function bundleRenderer(routeFile) {
531
+ async function bundleRenderer(routeFile, cacheTtl = 3600) {
543
532
  (0, import_node_fs2.mkdirSync)(BUNDLE_DIR, { recursive: true });
544
533
  const outfile = (0, import_node_path2.join)(BUNDLE_DIR, "renderer.js");
545
534
  const alias = {};
@@ -560,6 +549,9 @@ async function bundleRenderer(routeFile) {
560
549
  outfile,
561
550
  minify: true,
562
551
  alias,
552
+ define: {
553
+ __QLARA_CACHE_TTL__: String(cacheTtl)
554
+ },
563
555
  external: []
564
556
  });
565
557
  return createZip(outfile, "renderer.js");
@@ -776,6 +768,7 @@ function byoiResources(config) {
776
768
  function aws(awsConfig = {}) {
777
769
  const region = "us-east-1";
778
770
  const byoi = isByoi(awsConfig);
771
+ const cacheTtl = awsConfig.cacheTtl ?? 3600;
779
772
  const stackName = byoi ? "" : awsConfig.stackName || STACK_NAME_PREFIX;
780
773
  return {
781
774
  name: "aws",
@@ -857,7 +850,8 @@ function aws(awsConfig = {}) {
857
850
  const edgeZip = await bundleEdgeHandler({
858
851
  bucketName: res.bucketName,
859
852
  rendererArn: res.rendererFunctionArn,
860
- region: res.region
853
+ region: res.region,
854
+ cacheTtl
861
855
  });
862
856
  const lambda = new import_client_lambda.LambdaClient({ region: res.region });
863
857
  console.log("[qlara/aws] Waiting for edge handler to be ready...");
@@ -934,7 +928,7 @@ function aws(awsConfig = {}) {
934
928
  const cf = new import_client_cloudfront.CloudFrontClient({ region: res.region });
935
929
  await updateCloudFrontEdgeVersion(cf, res.distributionId, newVersionArn);
936
930
  console.log("[qlara/aws] Bundling renderer...");
937
- const rendererZip = await bundleRenderer(config.routeFile);
931
+ const rendererZip = await bundleRenderer(config.routeFile, cacheTtl);
938
932
  await (0, import_client_lambda.waitUntilFunctionUpdatedV2)(
939
933
  { client: lambda, maxWaitTime: 120 },
940
934
  { FunctionName: res.rendererFunctionArn }
@@ -1009,6 +1003,21 @@ function aws(awsConfig = {}) {
1009
1003
  `[qlara/aws] Could not update renderer permissions: ${err.message}`
1010
1004
  );
1011
1005
  }
1006
+ console.log("[qlara/aws] Warming up renderer...");
1007
+ try {
1008
+ await lambda.send(
1009
+ new import_client_lambda.InvokeCommand({
1010
+ FunctionName: res.rendererFunctionArn,
1011
+ InvocationType: "RequestResponse",
1012
+ Payload: JSON.stringify({ warmup: true })
1013
+ })
1014
+ );
1015
+ console.log("[qlara/aws] Renderer warmed up");
1016
+ } catch (err) {
1017
+ console.warn(
1018
+ `[qlara/aws] Renderer warm-up failed (non-critical): ${err.message}`
1019
+ );
1020
+ }
1012
1021
  console.log("[qlara/aws] Invalidating CloudFront cache...");
1013
1022
  await cf.send(
1014
1023
  new import_client_cloudfront.CreateInvalidationCommand({
package/dist/aws.d.cts CHANGED
@@ -3,6 +3,16 @@ import { P as ProviderResources, Q as QlaraProvider } from './types-gl2xFqEX.cjs
3
3
  interface AwsConfig {
4
4
  stackName?: string;
5
5
  bucketName?: string;
6
+ /**
7
+ * How long (in seconds) CloudFront should cache dynamically rendered pages.
8
+ * This sets the `s-maxage` value on the Cache-Control header.
9
+ *
10
+ * - Browsers always revalidate with CloudFront (`max-age=0`)
11
+ * - CloudFront serves from edge cache for this duration
12
+ *
13
+ * @default 3600 (1 hour)
14
+ */
15
+ cacheTtl?: number;
6
16
  distributionId?: string;
7
17
  distributionDomain?: string;
8
18
  edgeFunctionArn?: string;
package/dist/aws.d.ts CHANGED
@@ -3,6 +3,16 @@ import { P as ProviderResources, Q as QlaraProvider } from './types-gl2xFqEX.js'
3
3
  interface AwsConfig {
4
4
  stackName?: string;
5
5
  bucketName?: string;
6
+ /**
7
+ * How long (in seconds) CloudFront should cache dynamically rendered pages.
8
+ * This sets the `s-maxage` value on the Cache-Control header.
9
+ *
10
+ * - Browsers always revalidate with CloudFront (`max-age=0`)
11
+ * - CloudFront serves from edge cache for this duration
12
+ *
13
+ * @default 3600 (1 hour)
14
+ */
15
+ cacheTtl?: number;
6
16
  distributionId?: string;
7
17
  distributionDomain?: string;
8
18
  edgeFunctionArn?: string;
package/dist/aws.js CHANGED
@@ -1,7 +1,3 @@
1
- import {
2
- __require
3
- } from "./chunk-3RG5ZIWI.js";
4
-
5
1
  // src/provider/aws/index.ts
6
2
  import {
7
3
  CloudFormationClient,
@@ -19,6 +15,7 @@ import {
19
15
  UpdateFunctionConfigurationCommand,
20
16
  PublishVersionCommand,
21
17
  AddPermissionCommand,
18
+ InvokeCommand,
22
19
  waitUntilFunctionUpdatedV2
23
20
  } from "@aws-sdk/client-lambda";
24
21
  import {
@@ -464,41 +461,29 @@ import { build } from "esbuild";
464
461
  import { mkdirSync, existsSync } from "fs";
465
462
  import { resolve, join as join2, dirname } from "path";
466
463
  import { fileURLToPath } from "url";
464
+ import { createRequire } from "module";
467
465
  import archiver from "archiver";
468
466
  import { Writable } from "stream";
469
467
  var BUNDLE_DIR = join2(".qlara", "bundles");
470
- function getModuleDir() {
471
- if (typeof __dirname !== "undefined") {
472
- return __dirname;
473
- }
474
- return dirname(fileURLToPath(import.meta.url));
475
- }
476
- function findPackageRoot(startDir) {
477
- let dir = startDir;
478
- for (let i = 0; i < 10; i++) {
479
- const pkgPath = join2(dir, "package.json");
480
- if (existsSync(pkgPath)) {
481
- try {
482
- const { readFileSync: readFileSync3 } = __require("fs");
483
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
484
- if (pkg.name === "qlara") return dir;
485
- } catch {
486
- }
487
- }
488
- const parent = dirname(dir);
489
- if (parent === dir) break;
490
- dir = parent;
491
- }
492
- return startDir;
493
- }
494
468
  function resolveEntry(name) {
495
- const moduleDir = getModuleDir();
496
- const sameDirTs = resolve(moduleDir, `${name}.ts`);
469
+ const thisDir = dirname(fileURLToPath(import.meta.url));
470
+ const sameDirTs = resolve(thisDir, `${name}.ts`);
497
471
  if (existsSync(sameDirTs)) return sameDirTs;
498
- const pkgRoot = findPackageRoot(moduleDir);
499
- const pkgSrcTs = resolve(pkgRoot, "src", "provider", "aws", `${name}.ts`);
500
- if (existsSync(pkgSrcTs)) return pkgSrcTs;
501
- return resolve(moduleDir, `${name}.js`);
472
+ try {
473
+ const esmRequire = createRequire(import.meta.url);
474
+ const pkgJsonPath = esmRequire.resolve("qlara/package.json");
475
+ const pkgRoot = dirname(pkgJsonPath);
476
+ const srcTs = join2(pkgRoot, "src", "provider", "aws", `${name}.ts`);
477
+ if (existsSync(srcTs)) return srcTs;
478
+ } catch {
479
+ }
480
+ const fromDist = resolve(thisDir, "..", "src", "provider", "aws", `${name}.ts`);
481
+ if (existsSync(fromDist)) return fromDist;
482
+ throw new Error(
483
+ `[qlara] Could not find ${name}.ts Lambda source file. Searched:
484
+ ${sameDirTs}
485
+ ${fromDist}`
486
+ );
502
487
  }
503
488
  async function createZip(filePath, entryName) {
504
489
  return new Promise((resolvePromise, reject) => {
@@ -531,14 +516,15 @@ async function bundleEdgeHandler(config) {
531
516
  define: {
532
517
  __QLARA_BUCKET_NAME__: JSON.stringify(config.bucketName),
533
518
  __QLARA_RENDERER_ARN__: JSON.stringify(config.rendererArn),
534
- __QLARA_REGION__: JSON.stringify(config.region)
519
+ __QLARA_REGION__: JSON.stringify(config.region),
520
+ __QLARA_CACHE_TTL__: String(config.cacheTtl)
535
521
  },
536
522
  // Bundle everything — Lambda@Edge must be self-contained
537
523
  external: []
538
524
  });
539
525
  return createZip(outfile, "edge-handler.js");
540
526
  }
541
- async function bundleRenderer(routeFile) {
527
+ async function bundleRenderer(routeFile, cacheTtl = 3600) {
542
528
  mkdirSync(BUNDLE_DIR, { recursive: true });
543
529
  const outfile = join2(BUNDLE_DIR, "renderer.js");
544
530
  const alias = {};
@@ -559,6 +545,9 @@ async function bundleRenderer(routeFile) {
559
545
  outfile,
560
546
  minify: true,
561
547
  alias,
548
+ define: {
549
+ __QLARA_CACHE_TTL__: String(cacheTtl)
550
+ },
562
551
  external: []
563
552
  });
564
553
  return createZip(outfile, "renderer.js");
@@ -568,7 +557,7 @@ async function bundleRenderer(routeFile) {
568
557
  var STACK_NAME_PREFIX = "qlara";
569
558
 
570
559
  // src/fallback.ts
571
- import { readFileSync as readFileSync2, writeFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
560
+ import { readFileSync as readFileSync3, writeFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
572
561
  import { join as join3 } from "path";
573
562
  var FALLBACK_FILENAME = "_fallback.html";
574
563
  var FALLBACK_PLACEHOLDER = "__QLARA_FALLBACK__";
@@ -647,7 +636,7 @@ function generateFallbacks(buildDir, routes) {
647
636
  continue;
648
637
  }
649
638
  const templatePath = join3(routeDir, files[0]);
650
- const templateHtml = readFileSync2(templatePath, "utf-8");
639
+ const templateHtml = readFileSync3(templatePath, "utf-8");
651
640
  const fallbackHtml = generateFallbackFromTemplate(templateHtml, route.pattern);
652
641
  const fallbackPath = join3(routeDir, FALLBACK_FILENAME);
653
642
  writeFileSync(fallbackPath, fallbackHtml);
@@ -775,6 +764,7 @@ function byoiResources(config) {
775
764
  function aws(awsConfig = {}) {
776
765
  const region = "us-east-1";
777
766
  const byoi = isByoi(awsConfig);
767
+ const cacheTtl = awsConfig.cacheTtl ?? 3600;
778
768
  const stackName = byoi ? "" : awsConfig.stackName || STACK_NAME_PREFIX;
779
769
  return {
780
770
  name: "aws",
@@ -856,7 +846,8 @@ function aws(awsConfig = {}) {
856
846
  const edgeZip = await bundleEdgeHandler({
857
847
  bucketName: res.bucketName,
858
848
  rendererArn: res.rendererFunctionArn,
859
- region: res.region
849
+ region: res.region,
850
+ cacheTtl
860
851
  });
861
852
  const lambda = new LambdaClient({ region: res.region });
862
853
  console.log("[qlara/aws] Waiting for edge handler to be ready...");
@@ -933,7 +924,7 @@ function aws(awsConfig = {}) {
933
924
  const cf = new CloudFrontClient({ region: res.region });
934
925
  await updateCloudFrontEdgeVersion(cf, res.distributionId, newVersionArn);
935
926
  console.log("[qlara/aws] Bundling renderer...");
936
- const rendererZip = await bundleRenderer(config.routeFile);
927
+ const rendererZip = await bundleRenderer(config.routeFile, cacheTtl);
937
928
  await waitUntilFunctionUpdatedV2(
938
929
  { client: lambda, maxWaitTime: 120 },
939
930
  { FunctionName: res.rendererFunctionArn }
@@ -1008,6 +999,21 @@ function aws(awsConfig = {}) {
1008
999
  `[qlara/aws] Could not update renderer permissions: ${err.message}`
1009
1000
  );
1010
1001
  }
1002
+ console.log("[qlara/aws] Warming up renderer...");
1003
+ try {
1004
+ await lambda.send(
1005
+ new InvokeCommand({
1006
+ FunctionName: res.rendererFunctionArn,
1007
+ InvocationType: "RequestResponse",
1008
+ Payload: JSON.stringify({ warmup: true })
1009
+ })
1010
+ );
1011
+ console.log("[qlara/aws] Renderer warmed up");
1012
+ } catch (err) {
1013
+ console.warn(
1014
+ `[qlara/aws] Renderer warm-up failed (non-critical): ${err.message}`
1015
+ );
1016
+ }
1011
1017
  console.log("[qlara/aws] Invalidating CloudFront cache...");
1012
1018
  await cf.send(
1013
1019
  new CreateInvalidationCommand({
package/dist/cli.js CHANGED
@@ -1,13 +1,7 @@
1
1
  #!/usr/bin/env node
2
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
- }) : x)(function(x) {
5
- if (typeof require !== "undefined") return require.apply(this, arguments);
6
- throw Error('Dynamic require of "' + x + '" is not supported');
7
- });
8
2
 
9
3
  // src/cli.ts
10
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
4
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
11
5
  import { join as join4 } from "path";
12
6
 
13
7
  // src/provider/aws/constants.ts
@@ -32,6 +26,7 @@ import {
32
26
  UpdateFunctionConfigurationCommand,
33
27
  PublishVersionCommand,
34
28
  AddPermissionCommand,
29
+ InvokeCommand,
35
30
  waitUntilFunctionUpdatedV2
36
31
  } from "@aws-sdk/client-lambda";
37
32
  import {
@@ -477,41 +472,29 @@ import { build } from "esbuild";
477
472
  import { mkdirSync, existsSync } from "fs";
478
473
  import { resolve, join as join2, dirname } from "path";
479
474
  import { fileURLToPath } from "url";
475
+ import { createRequire } from "module";
480
476
  import archiver from "archiver";
481
477
  import { Writable } from "stream";
482
478
  var BUNDLE_DIR = join2(".qlara", "bundles");
483
- function getModuleDir() {
484
- if (typeof __dirname !== "undefined") {
485
- return __dirname;
486
- }
487
- return dirname(fileURLToPath(import.meta.url));
488
- }
489
- function findPackageRoot(startDir) {
490
- let dir = startDir;
491
- for (let i = 0; i < 10; i++) {
492
- const pkgPath = join2(dir, "package.json");
493
- if (existsSync(pkgPath)) {
494
- try {
495
- const { readFileSync: readFileSync4 } = __require("fs");
496
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
497
- if (pkg.name === "qlara") return dir;
498
- } catch {
499
- }
500
- }
501
- const parent = dirname(dir);
502
- if (parent === dir) break;
503
- dir = parent;
504
- }
505
- return startDir;
506
- }
507
479
  function resolveEntry(name) {
508
- const moduleDir = getModuleDir();
509
- const sameDirTs = resolve(moduleDir, `${name}.ts`);
480
+ const thisDir = dirname(fileURLToPath(import.meta.url));
481
+ const sameDirTs = resolve(thisDir, `${name}.ts`);
510
482
  if (existsSync(sameDirTs)) return sameDirTs;
511
- const pkgRoot = findPackageRoot(moduleDir);
512
- const pkgSrcTs = resolve(pkgRoot, "src", "provider", "aws", `${name}.ts`);
513
- if (existsSync(pkgSrcTs)) return pkgSrcTs;
514
- return resolve(moduleDir, `${name}.js`);
483
+ try {
484
+ const esmRequire = createRequire(import.meta.url);
485
+ const pkgJsonPath = esmRequire.resolve("qlara/package.json");
486
+ const pkgRoot = dirname(pkgJsonPath);
487
+ const srcTs = join2(pkgRoot, "src", "provider", "aws", `${name}.ts`);
488
+ if (existsSync(srcTs)) return srcTs;
489
+ } catch {
490
+ }
491
+ const fromDist = resolve(thisDir, "..", "src", "provider", "aws", `${name}.ts`);
492
+ if (existsSync(fromDist)) return fromDist;
493
+ throw new Error(
494
+ `[qlara] Could not find ${name}.ts Lambda source file. Searched:
495
+ ${sameDirTs}
496
+ ${fromDist}`
497
+ );
515
498
  }
516
499
  async function createZip(filePath, entryName) {
517
500
  return new Promise((resolvePromise, reject) => {
@@ -544,14 +527,15 @@ async function bundleEdgeHandler(config) {
544
527
  define: {
545
528
  __QLARA_BUCKET_NAME__: JSON.stringify(config.bucketName),
546
529
  __QLARA_RENDERER_ARN__: JSON.stringify(config.rendererArn),
547
- __QLARA_REGION__: JSON.stringify(config.region)
530
+ __QLARA_REGION__: JSON.stringify(config.region),
531
+ __QLARA_CACHE_TTL__: String(config.cacheTtl)
548
532
  },
549
533
  // Bundle everything — Lambda@Edge must be self-contained
550
534
  external: []
551
535
  });
552
536
  return createZip(outfile, "edge-handler.js");
553
537
  }
554
- async function bundleRenderer(routeFile) {
538
+ async function bundleRenderer(routeFile, cacheTtl = 3600) {
555
539
  mkdirSync(BUNDLE_DIR, { recursive: true });
556
540
  const outfile = join2(BUNDLE_DIR, "renderer.js");
557
541
  const alias = {};
@@ -572,13 +556,16 @@ async function bundleRenderer(routeFile) {
572
556
  outfile,
573
557
  minify: true,
574
558
  alias,
559
+ define: {
560
+ __QLARA_CACHE_TTL__: String(cacheTtl)
561
+ },
575
562
  external: []
576
563
  });
577
564
  return createZip(outfile, "renderer.js");
578
565
  }
579
566
 
580
567
  // src/fallback.ts
581
- import { readFileSync as readFileSync2, writeFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
568
+ import { readFileSync as readFileSync3, writeFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
582
569
  import { join as join3 } from "path";
583
570
  var FALLBACK_FILENAME = "_fallback.html";
584
571
  var FALLBACK_PLACEHOLDER = "__QLARA_FALLBACK__";
@@ -657,7 +644,7 @@ function generateFallbacks(buildDir, routes) {
657
644
  continue;
658
645
  }
659
646
  const templatePath = join3(routeDir, files[0]);
660
- const templateHtml = readFileSync2(templatePath, "utf-8");
647
+ const templateHtml = readFileSync3(templatePath, "utf-8");
661
648
  const fallbackHtml = generateFallbackFromTemplate(templateHtml, route.pattern);
662
649
  const fallbackPath = join3(routeDir, FALLBACK_FILENAME);
663
650
  writeFileSync(fallbackPath, fallbackHtml);
@@ -785,6 +772,7 @@ function byoiResources(config) {
785
772
  function aws(awsConfig = {}) {
786
773
  const region = "us-east-1";
787
774
  const byoi = isByoi(awsConfig);
775
+ const cacheTtl = awsConfig.cacheTtl ?? 3600;
788
776
  const stackName = byoi ? "" : awsConfig.stackName || STACK_NAME_PREFIX;
789
777
  return {
790
778
  name: "aws",
@@ -866,7 +854,8 @@ function aws(awsConfig = {}) {
866
854
  const edgeZip = await bundleEdgeHandler({
867
855
  bucketName: res.bucketName,
868
856
  rendererArn: res.rendererFunctionArn,
869
- region: res.region
857
+ region: res.region,
858
+ cacheTtl
870
859
  });
871
860
  const lambda = new LambdaClient({ region: res.region });
872
861
  console.log("[qlara/aws] Waiting for edge handler to be ready...");
@@ -943,7 +932,7 @@ function aws(awsConfig = {}) {
943
932
  const cf = new CloudFrontClient({ region: res.region });
944
933
  await updateCloudFrontEdgeVersion(cf, res.distributionId, newVersionArn);
945
934
  console.log("[qlara/aws] Bundling renderer...");
946
- const rendererZip = await bundleRenderer(config.routeFile);
935
+ const rendererZip = await bundleRenderer(config.routeFile, cacheTtl);
947
936
  await waitUntilFunctionUpdatedV2(
948
937
  { client: lambda, maxWaitTime: 120 },
949
938
  { FunctionName: res.rendererFunctionArn }
@@ -1018,6 +1007,21 @@ function aws(awsConfig = {}) {
1018
1007
  `[qlara/aws] Could not update renderer permissions: ${err.message}`
1019
1008
  );
1020
1009
  }
1010
+ console.log("[qlara/aws] Warming up renderer...");
1011
+ try {
1012
+ await lambda.send(
1013
+ new InvokeCommand({
1014
+ FunctionName: res.rendererFunctionArn,
1015
+ InvocationType: "RequestResponse",
1016
+ Payload: JSON.stringify({ warmup: true })
1017
+ })
1018
+ );
1019
+ console.log("[qlara/aws] Renderer warmed up");
1020
+ } catch (err) {
1021
+ console.warn(
1022
+ `[qlara/aws] Renderer warm-up failed (non-critical): ${err.message}`
1023
+ );
1024
+ }
1021
1025
  console.log("[qlara/aws] Invalidating CloudFront cache...");
1022
1026
  await cf.send(
1023
1027
  new CreateInvalidationCommand({
@@ -1101,11 +1105,11 @@ function loadConfig() {
1101
1105
  console.error("[qlara] Run your framework build first (e.g. `next build`)");
1102
1106
  process.exit(1);
1103
1107
  }
1104
- return JSON.parse(readFileSync3(CONFIG_PATH, "utf-8"));
1108
+ return JSON.parse(readFileSync4(CONFIG_PATH, "utf-8"));
1105
1109
  }
1106
1110
  function loadResources() {
1107
1111
  if (!existsSync3(RESOURCES_PATH)) return null;
1108
- return JSON.parse(readFileSync3(RESOURCES_PATH, "utf-8"));
1112
+ return JSON.parse(readFileSync4(RESOURCES_PATH, "utf-8"));
1109
1113
  }
1110
1114
  function saveResources(resources) {
1111
1115
  mkdirSync2(QLARA_DIR, { recursive: true });
package/dist/index.js CHANGED
@@ -5,7 +5,6 @@ import {
5
5
  patternToRegex,
6
6
  validateConfig
7
7
  } from "./chunk-5K3GS3TV.js";
8
- import "./chunk-3RG5ZIWI.js";
9
8
  export {
10
9
  buildManifest,
11
10
  extractParamNames,
@@ -2,7 +2,6 @@ import {
2
2
  buildManifest,
3
3
  validateConfig
4
4
  } from "../chunk-5K3GS3TV.js";
5
- import "../chunk-3RG5ZIWI.js";
6
5
 
7
6
  // src/plugin/next.ts
8
7
  import { writeFileSync, readFileSync, mkdirSync, existsSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qlara",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Runtime ISR for static React apps — dynamic routing and SEO metadata for statically exported Next.js apps on AWS",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -42,7 +42,8 @@
42
42
  "types": "./dist/aws.d.ts",
43
43
  "import": "./dist/aws.js",
44
44
  "require": "./dist/aws.cjs"
45
- }
45
+ },
46
+ "./package.json": "./package.json"
46
47
  },
47
48
  "files": [
48
49
  "dist",
@@ -24,6 +24,7 @@ import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
24
24
  declare const __QLARA_BUCKET_NAME__: string;
25
25
  declare const __QLARA_RENDERER_ARN__: string;
26
26
  declare const __QLARA_REGION__: string;
27
+ declare const __QLARA_CACHE_TTL__: number;
27
28
  // ── Types (inlined to keep bundle self-contained) ────────────────
28
29
 
29
30
  interface ManifestRoute {
@@ -238,7 +239,7 @@ function buildHtmlResponse(
238
239
  { key: 'Content-Type', value: 'text/html; charset=utf-8' },
239
240
  ],
240
241
  'cache-control': [
241
- { key: 'Cache-Control', value: 'public, max-age=0, must-revalidate' },
242
+ { key: 'Cache-Control', value: `public, max-age=0, s-maxage=${__QLARA_CACHE_TTL__}, stale-while-revalidate=60` },
242
243
  ],
243
244
  };
244
245
 
@@ -52,6 +52,9 @@ import type {
52
52
  // The routes module is resolved by esbuild at deploy time.
53
53
  // esbuild's `alias` option maps this import to the developer's route file.
54
54
  // At bundle time: '__qlara_routes__' → './qlara.routes.ts' (or wherever the dev put it)
55
+ // Injected at bundle time by esbuild define
56
+ declare const __QLARA_CACHE_TTL__: number;
57
+
55
58
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
56
59
  // @ts-ignore — resolved at bundle time by esbuild alias
57
60
  import routes from '__qlara_routes__';
@@ -752,20 +755,30 @@ function metadataToRscEntries(metadata: QlaraMetadata): string {
752
755
  return entries.join('');
753
756
  }
754
757
 
755
- export async function handler(event: RendererEvent): Promise<RendererResult> {
758
+ export async function handler(event: RendererEvent & { warmup?: boolean }): Promise<RendererResult> {
759
+ // Warmup invocation — just initialize the runtime and return
760
+ if (event.warmup) {
761
+ return { statusCode: 200, body: 'warm' };
762
+ }
763
+
756
764
  const { uri, bucket, routePattern, params } = event;
757
765
  const region = process.env.AWS_REGION || 'us-east-1';
758
766
 
759
767
  const s3 = new S3Client({ region });
760
768
 
761
769
  try {
762
- // 0. Check if the page was already rendered (guards against duplicate concurrent requests)
770
+ // 0. Check if already rendered + read fallback in parallel
763
771
  const s3Key = deriveS3Key(uri);
764
- try {
765
- const existing = await s3.send(
766
- new GetObjectCommand({ Bucket: bucket, Key: s3Key })
767
- );
768
- const existingHtml = await existing.Body?.transformToString('utf-8');
772
+ const fallbackKey = deriveFallbackKey(uri);
773
+
774
+ const [existingResult, fallbackResult] = await Promise.allSettled([
775
+ s3.send(new GetObjectCommand({ Bucket: bucket, Key: s3Key })),
776
+ s3.send(new GetObjectCommand({ Bucket: bucket, Key: fallbackKey })),
777
+ ]);
778
+
779
+ // If page already exists, return it (guards against duplicate concurrent requests)
780
+ if (existingResult.status === 'fulfilled') {
781
+ const existingHtml = await existingResult.value.Body?.transformToString('utf-8');
769
782
  if (existingHtml) {
770
783
  return {
771
784
  statusCode: 200,
@@ -773,26 +786,20 @@ export async function handler(event: RendererEvent): Promise<RendererResult> {
773
786
  html: existingHtml,
774
787
  };
775
788
  }
776
- } catch {
777
- // Page doesn't exist yet — continue with rendering
778
789
  }
779
790
 
780
- // 1. Read the fallback HTML from S3
781
- const fallbackKey = deriveFallbackKey(uri);
791
+ // Extract fallback HTML
782
792
  let fallbackHtml: string;
783
793
 
784
- try {
785
- const response = await s3.send(
786
- new GetObjectCommand({ Bucket: bucket, Key: fallbackKey })
787
- );
788
- fallbackHtml = (await response.Body?.transformToString('utf-8')) || '';
794
+ if (fallbackResult.status === 'fulfilled') {
795
+ fallbackHtml = (await fallbackResult.value.Body?.transformToString('utf-8')) || '';
789
796
  if (!fallbackHtml) {
790
797
  return {
791
798
  statusCode: 500,
792
799
  body: JSON.stringify({ error: `Empty fallback at ${fallbackKey}` }),
793
800
  };
794
801
  }
795
- } catch {
802
+ } else {
796
803
  return {
797
804
  statusCode: 500,
798
805
  body: JSON.stringify({ error: `Fallback not found: ${fallbackKey}` }),
@@ -824,8 +831,7 @@ export async function handler(event: RendererEvent): Promise<RendererResult> {
824
831
  Key: s3Key,
825
832
  Body: html,
826
833
  ContentType: 'text/html; charset=utf-8',
827
- CacheControl:
828
- 'public, max-age=0, s-maxage=31536000, stale-while-revalidate=86400',
834
+ CacheControl: `public, max-age=0, s-maxage=${__QLARA_CACHE_TTL__}, stale-while-revalidate=60`,
829
835
  })
830
836
  );
831
837
 
@@ -1,10 +0,0 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
- export {
9
- __require
10
- };