lambder 5.0.0 → 5.1.2

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/README.md CHANGED
@@ -53,8 +53,9 @@ const company = await caller.api("getCompany", { slug: "acme" });
53
53
  cookies, and a guard against Lambda's response size cap.
54
54
  - **Hooks and actions.** Lifecycle hooks, plus `addAction()` for the non-HTTP
55
55
  invocations (EventBridge, SQS, custom events) the same function receives.
56
- - **Frontend hosting.** Serve a build from a folder, S3 or R2, with an app
57
- shell rendered through a build-pipeline-safe template engine.
56
+ - **Frontend hosting.** Serve a build from a folder, S3, R2 or any HTTP
57
+ origin, with an app shell rendered through a build-pipeline-safe template
58
+ engine.
58
59
  - **Runs anywhere Lambda does.** API Gateway REST APIs (payload v1), HTTP APIs
59
60
  (payload v2) and Lambda Function URLs; the payload format is detected per
60
61
  event.
@@ -9,9 +9,10 @@ export type LambderFile = {
9
9
  * servePublicFiles, serveIndexHtml, res.file and res.templateFile alike,
10
10
  * through the instance's one reader (LambderFiles). Implement `read` over
11
11
  * any backing store: LambderLocalFileSource (a folder), LambderS3FileSource
12
- * (S3, or R2 and other S3-compatible stores), or your own. The reader does
13
- * the rest for every source: traversal check, memory cache, mime fallback
14
- * from the extension.
12
+ * (S3, or R2 and other S3-compatible stores), LambderHttpFileSource (any
13
+ * origin serving files by path), or your own. The reader does the rest for
14
+ * every source: traversal check, memory cache, mime fallback from the
15
+ * extension.
15
16
  */
16
17
  export interface LambderFileSource {
17
18
  /**
@@ -48,6 +49,12 @@ export declare class LambderLocalFileSource implements LambderFileSource {
48
49
  });
49
50
  read(relativePath: string): Promise<LambderFile | null>;
50
51
  }
52
+ /**
53
+ * A file a remote store returned: the store's Content-Type unless it is a
54
+ * generic octet-stream, in which case the extension decides, as for local
55
+ * files.
56
+ */
57
+ export declare const remoteStoreFile: (body: Buffer, contentType: string | null | undefined) => LambderFile;
51
58
  /**
52
59
  * The path a source is asked for: leading slash stripped, traversal
53
60
  * rejected; null for a path that names no file (empty, or a directory).
@@ -27,6 +27,12 @@ export class LambderLocalFileSource {
27
27
  return { body: await fs.promises.readFile(absolute) };
28
28
  }
29
29
  }
30
+ /**
31
+ * A file a remote store returned: the store's Content-Type unless it is a
32
+ * generic octet-stream, in which case the extension decides, as for local
33
+ * files.
34
+ */
35
+ export const remoteStoreFile = (body, contentType) => contentType && !contentType.endsWith("octet-stream") ? { body, mimeType: contentType } : { body };
30
36
  /**
31
37
  * The path a source is asked for: leading slash stripped, traversal
32
38
  * rejected; null for a path that names no file (empty, or a directory).
package/dist/index.d.ts CHANGED
@@ -21,6 +21,8 @@ export { LambderFiles, LambderLocalFileSource } from "./core/LambderFiles.js";
21
21
  export type { LambderFileSource, LambderFile, LambderFilesOption, LambderFileMemoryCacheOption, LambderReadFile } from "./core/LambderFiles.js";
22
22
  export { LambderS3FileSource } from "./stores/LambderS3FileSource.js";
23
23
  export type { LambderS3FileSourceOptions } from "./stores/LambderS3FileSource.js";
24
+ export { LambderHttpFileSource } from "./stores/LambderHttpFileSource.js";
25
+ export type { LambderHttpFileSourceOptions } from "./stores/LambderHttpFileSource.js";
24
26
  export type { LambderSessionCookieOptions } from "./session/LambderSessionController.js";
25
27
  export type { LambderSessionContext, LambderCreatedSession, LambderSessionDataRefreshConfig } from "./session/LambderSessionManager.js";
26
28
  export { resolveCompressionOption, LAMBDER_ENCODINGS } from "./shared/LambderCompressionOption.js";
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ export { LambderTemplatingEngine } from "./core/LambderTemplatingEngine.js";
18
18
  export { LambderPublicFilesHandler } from "./core/LambderPublicFiles.js";
19
19
  export { LambderFiles, LambderLocalFileSource } from "./core/LambderFiles.js";
20
20
  export { LambderS3FileSource } from "./stores/LambderS3FileSource.js";
21
+ export { LambderHttpFileSource } from "./stores/LambderHttpFileSource.js";
21
22
  // Compression: the option every site shares, and the one codec behind them all.
22
23
  export { resolveCompressionOption, LAMBDER_ENCODINGS } from "./shared/LambderCompressionOption.js";
23
24
  // Brotli/gzip plus the bounded, length-verified restore every compressed
@@ -0,0 +1,31 @@
1
+ import { type LambderFile, type LambderFileSource } from "../core/LambderFiles.js";
2
+ export type LambderHttpFileSourceOptions = {
3
+ /**
4
+ * The folder URL relative paths resolve under: "https://assets.example.com/v42/".
5
+ * It names a folder, so a missing trailing slash is added.
6
+ */
7
+ baseUrl: string;
8
+ /** Sent with every read, e.g. an Authorization header for a private origin or a User-Agent a firewall allows. */
9
+ headers?: Record<string, string>;
10
+ /** How long one read may take before it fails. Default: 10000. */
11
+ timeoutMs?: number;
12
+ };
13
+ /**
14
+ * Files over HTTP(S) from any origin that serves them by path: a CDN, a
15
+ * public bucket's own domain (a Cloudflare R2 custom domain, an S3 website
16
+ * endpoint) or another server. Reads with the runtime's fetch, so it needs
17
+ * no SDK, and no credentials for a public origin; reads come out of the
18
+ * origin's edge cache. Each path segment is percent-encoded, so a relative
19
+ * path names the same object it would as an S3 key. A 404 or 410 reads as
20
+ * null and the request falls through; any other failed status, a network
21
+ * error or a timeout propagates as an error. The response's Content-Type is
22
+ * used unless it is a generic octet-stream, in which case the extension
23
+ * decides, as for local files.
24
+ */
25
+ export declare class LambderHttpFileSource implements LambderFileSource {
26
+ private readonly baseUrl;
27
+ private readonly headers;
28
+ private readonly timeoutMs;
29
+ constructor({ baseUrl, headers, timeoutMs }: LambderHttpFileSourceOptions);
30
+ read(relativePath: string): Promise<LambderFile | null>;
31
+ }
@@ -0,0 +1,46 @@
1
+ import { remoteStoreFile } from "../core/LambderFiles.js";
2
+ const DEFAULT_TIMEOUT_MS = 10_000;
3
+ /**
4
+ * Files over HTTP(S) from any origin that serves them by path: a CDN, a
5
+ * public bucket's own domain (a Cloudflare R2 custom domain, an S3 website
6
+ * endpoint) or another server. Reads with the runtime's fetch, so it needs
7
+ * no SDK, and no credentials for a public origin; reads come out of the
8
+ * origin's edge cache. Each path segment is percent-encoded, so a relative
9
+ * path names the same object it would as an S3 key. A 404 or 410 reads as
10
+ * null and the request falls through; any other failed status, a network
11
+ * error or a timeout propagates as an error. The response's Content-Type is
12
+ * used unless it is a generic octet-stream, in which case the extension
13
+ * decides, as for local files.
14
+ */
15
+ export class LambderHttpFileSource {
16
+ baseUrl;
17
+ headers;
18
+ timeoutMs;
19
+ constructor({ baseUrl, headers = {}, timeoutMs = DEFAULT_TIMEOUT_MS }) {
20
+ let url;
21
+ try {
22
+ url = new URL(baseUrl);
23
+ }
24
+ catch {
25
+ throw new Error(`baseUrl must be an absolute http(s) URL: ${baseUrl}`);
26
+ }
27
+ if (url.protocol !== "http:" && url.protocol !== "https:")
28
+ throw new Error(`baseUrl must be an absolute http(s) URL: ${baseUrl}`);
29
+ if (!url.pathname.endsWith("/"))
30
+ url.pathname += "/";
31
+ this.baseUrl = url;
32
+ this.headers = headers;
33
+ this.timeoutMs = timeoutMs;
34
+ }
35
+ async read(relativePath) {
36
+ const url = new URL(relativePath.split("/").map(encodeURIComponent).join("/"), this.baseUrl);
37
+ const response = await fetch(url, { headers: this.headers, signal: AbortSignal.timeout(this.timeoutMs) });
38
+ if (!response.ok) {
39
+ await response.body?.cancel();
40
+ if (response.status === 404 || response.status === 410)
41
+ return null;
42
+ throw new Error(`LambderHttpFileSource: ${response.status} ${response.statusText} reading ${url}`);
43
+ }
44
+ return remoteStoreFile(Buffer.from(await response.arrayBuffer()), response.headers.get("content-type"));
45
+ }
46
+ }
@@ -1,5 +1,5 @@
1
1
  import type { S3Client, S3ClientConfig } from "@aws-sdk/client-s3";
2
- import type { LambderFile, LambderFileSource } from "../core/LambderFiles.js";
2
+ import { type LambderFile, type LambderFileSource } from "../core/LambderFiles.js";
3
3
  export type LambderS3FileSourceOptions = {
4
4
  bucket: string;
5
5
  /** Literal key prefix the relative path is appended to, so include the trailing slash: "web/v42/". Default: none. */
@@ -1,3 +1,4 @@
1
+ import { remoteStoreFile } from "../core/LambderFiles.js";
1
2
  /**
2
3
  * Files from an S3 bucket, or any S3-compatible store such as Cloudflare
3
4
  * R2 (pass its endpoint in clientConfig). Needs @aws-sdk/client-s3, an
@@ -46,8 +47,6 @@ export class LambderS3FileSource {
46
47
  }
47
48
  if (!output.Body)
48
49
  return null;
49
- const body = Buffer.from(await output.Body.transformToByteArray());
50
- const contentType = output.ContentType;
51
- return contentType && !contentType.endsWith("octet-stream") ? { body, mimeType: contentType } : { body };
50
+ return remoteStoreFile(Buffer.from(await output.Body.transformToByteArray()), output.ContentType);
52
51
  }
53
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "5.0.0",
3
+ "version": "5.1.2",
4
4
  "description": "Opinionated serverless web framework for TypeScript on AWS Lambda: type-safe APIs from Zod schemas, DynamoDB sessions, and declarative rate limits, authorization guards and idempotency.",
5
5
  "keywords": [
6
6
  "lambda",