sst-http 1.3.2 → 1.3.3-beta.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # sst-http
2
2
 
3
- Decorator-based HTTP routing for [SST v3](https://sst.dev) that keeps your app on a single Lambda handler while still wiring routes directly into API Gateway. Build routes with NestJS-style decorators, secure them with Firebase JWT authorizers, and generate an infra-ready manifest from your source.
3
+ Decorator-based HTTP routing and EventBridge helpers for SST v3. Keep a single Lambda for your API, scan routes into a manifest, and wire everything into API Gateway. The bus helpers let you subscribe handlers with `@On()` and publish events from anywhere.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,13 +8,44 @@ Decorator-based HTTP routing for [SST v3](https://sst.dev) that keeps your app o
8
8
  pnpm add sst-http
9
9
  ```
10
10
 
11
- ## Define Routes
11
+ ## Import style
12
12
 
13
- Create routed functions anywhere in your project no controller classes required.
13
+ You can keep using the root entrypoint, or import by domain:
14
+
15
+ ```ts
16
+ import { createHandler, Get, Post } from "sst-http/http";
17
+ import { On, publish } from "sst-http/bus";
18
+
19
+ // Root entrypoint also works
20
+ import { createHandler as createHttpHandler } from "sst-http";
21
+ ```
22
+
23
+ ## Examples
24
+
25
+ The repo ships three SST v3 examples under `examples/`:
26
+
27
+ - `examples/http`: four HTTP routes (path param, query string, JSON body, plus a ping route).
28
+ - `examples/bus-publisher`: exposes `GET /` and publishes a `demo.created` event to the bus.
29
+ - `examples/bus-receiver`: a single `@On("demo.created")` handler that receives the event.
30
+
31
+ To run one of them:
32
+
33
+ ```bash
34
+ cd examples/http
35
+ pnpm install
36
+ pnpm run routes:scan
37
+ pnpm run dev
38
+ ```
39
+
40
+ For the bus pair, you can deploy either example in any order since events target the default bus.
41
+
42
+ ## HTTP routes
43
+
44
+ Create routed functions anywhere in your project—no controllers required.
14
45
 
15
46
  ```ts
16
47
  // src/routes/users.ts
17
- import { Get, Post, FirebaseAuth, json } from "sst-http";
48
+ import { Get, Post, FirebaseAuth, json } from "sst-http/http";
18
49
 
19
50
  export class UserRoutes {
20
51
  @Get("/users/{id}")
@@ -34,18 +65,18 @@ export const getUser = UserRoutes.getUser;
34
65
  export const createUser = UserRoutes.createUser;
35
66
  ```
36
67
 
37
- Enable name-based inference once at bootstrap if you prefer omitting explicit path strings:
68
+ Enable name-based inference if you prefer omitting explicit paths:
38
69
 
39
70
  ```ts
40
- import { configureRoutes } from "sst-http";
71
+ import { configureRoutes } from "sst-http/http";
41
72
 
42
73
  configureRoutes({ inferPathFromName: true });
43
74
  ```
44
75
 
45
- Parameter decorators are available when you want granular control:
76
+ ### Parameter decorators
46
77
 
47
78
  ```ts
48
- import { Body, Post } from "sst-http";
79
+ import { Body, Post } from "sst-http/http";
49
80
  import { z } from "zod/v4";
50
81
 
51
82
  const CreateTodo = z.object({ title: z.string().min(1) });
@@ -53,7 +84,6 @@ const CreateTodo = z.object({ title: z.string().min(1) });
53
84
  export class TodoRoutes {
54
85
  @Post("/todos")
55
86
  static createTodo(@Body(CreateTodo) payload: z.infer<typeof CreateTodo>) {
56
- // payload is validated JSON
57
87
  return { statusCode: 201, body: JSON.stringify(payload) };
58
88
  }
59
89
  }
@@ -62,16 +92,16 @@ export const createTodo = TodoRoutes.createTodo;
62
92
  ```
63
93
 
64
94
  > **Note**
65
- > API Gateway route keys expect `{param}` placeholders. The router accepts either `{param}` or `:param` at runtime, but manifests and infra wiring emit `{param}` so your deployed routes line up with AWS.
95
+ > API Gateway route keys expect `{param}` placeholders. The router accepts `{param}` or `:param` at runtime, but manifests and infra wiring emit `{param}` so your deployed routes line up with AWS.
66
96
 
67
- ## Single Lambda Entry
97
+ ## Single Lambda entry
68
98
 
69
- All decorated modules register themselves on import. The single exported handler performs routing and response formatting for both REST and HTTP API Gateway payloads.
99
+ All decorated modules register themselves on import. The handler handles routing for both REST and HTTP API Gateway payloads.
70
100
 
71
101
  ```ts
72
102
  // src/server.ts
73
103
  import "reflect-metadata";
74
- import { createHandler } from "sst-http";
104
+ import { createHandler } from "sst-http/http";
75
105
 
76
106
  import "./routes/users";
77
107
  import "./routes/health";
@@ -79,25 +109,56 @@ import "./routes/health";
79
109
  export const handler = createHandler();
80
110
  ```
81
111
 
82
- Helpers such as `json()`, `text()`, and `noContent()` are available for concise responses, and thrown `HttpError` instances are turned into normalized JSON error payloads.
112
+ Helpers such as `json()`, `text()`, and `noContent()` are available for concise responses. Throw `HttpError` to return a normalized JSON error payload.
113
+
114
+ ## Event bus
83
115
 
84
- ## Scan & Manifest
116
+ Use `@On()` to register EventBridge handlers and `publish()` to emit events.
85
117
 
86
- Use the CLI to inspect your source tree and materialize a routes manifest for infra wiring.
118
+ ```ts
119
+ // src/events/user-events.ts
120
+ import { On } from "sst-http/bus";
121
+
122
+ export class UserEvents {
123
+ @On("user.created")
124
+ static async onUserCreated(detail: { id: string }) {
125
+ console.log("New user", detail.id);
126
+ }
127
+ }
128
+
129
+ export const onUserCreated = UserEvents.onUserCreated;
130
+ ```
131
+
132
+ ```ts
133
+ import { publish } from "sst-http/bus";
134
+
135
+ await publish("user.created", { id: "123" });
136
+ ```
137
+
138
+ `publish()` signs requests with the current AWS credentials and requires `AWS_REGION`/`AWS_DEFAULT_REGION` in the environment.
139
+
140
+ ## Scan & manifest
141
+
142
+ Use the CLI to inspect your source tree and materialize a manifest for infra wiring.
87
143
 
88
144
  ```bash
89
- pnpm sst-http scan --glob "src/routes/**/*.ts" --out routes.manifest.json
145
+ pnpm sst-http scan --glob "src/**/*.ts" --out routes.manifest.json
90
146
  ```
91
147
 
92
- Pass `--infer-name` to map routes without explicit paths using the kebab-case function name (matching the runtime `configureRoutes({ inferPathFromName: true })`).
148
+ Pass `--infer-name` to map routes without explicit paths using the kebab-case function name (matching `configureRoutes({ inferPathFromName: true })`). When `@On()` is used, events are emitted into the same manifest under `events`.
149
+
150
+ ## Firebase JWT authorizer
93
151
 
94
- ## Firebase JWT Authorizer
152
+ Mark a route with `@FirebaseAuth()` and the manifest records it as protected. The wiring utilities configure an API Gateway JWT authorizer using:
95
153
 
96
- Mark a route with `@FirebaseAuth()` and the manifest records it as protected. The core wiring function sets up an API Gateway JWT authorizer that points at your Firebase project (issuer `https://securetoken.google.com/<projectId>` and matching audience). Optional roles and optional-auth flags flow through to the adapter so you can fine-tune scopes.
154
+ - Issuer: `https://securetoken.google.com/<projectId>`
155
+ - Audience: `<projectId>`
97
156
 
98
- ## Wire API Gateway
157
+ Optional roles and optional-auth flags flow into the adapter so you can fine-tune scopes.
99
158
 
100
- `sst-http/infra` ships with a manifest-driven wiring utility plus adapters for HTTP API (ApiGatewayV2) and REST API (ApiGateway). The example below wires all routes to a single Lambda function inside `sst.config.ts`.
159
+ ## Wire API Gateway + EventBridge
160
+
161
+ `sst-http/infra` ships with a manifest-driven wiring utility plus adapters for HTTP API (ApiGatewayV2) and REST API (ApiGateway). The example below wires all routes to a single Lambda function inside `sst.config.ts` and connects event subscriptions from the same manifest.
101
162
 
102
163
  ```ts
103
164
  // sst.config.ts
@@ -110,12 +171,12 @@ export default $config({
110
171
  loadRoutesManifest,
111
172
  wireApiFromManifest,
112
173
  httpApiAdapter,
174
+ createBus,
113
175
  } = await import("sst-http/infra");
114
176
 
115
177
  const manifest = loadRoutesManifest("routes.manifest.json");
116
178
  const { api, registerRoute, ensureJwtAuthorizer } = httpApiAdapter({ apiName: "Api" });
117
179
 
118
- // Single Lambda for all routes
119
180
  const handler = new sst.aws.Function("Handler", {
120
181
  handler: "src/server.handler",
121
182
  runtime: "nodejs20.x",
@@ -123,11 +184,14 @@ export default $config({
123
184
  memory: "512 MB",
124
185
  });
125
186
 
187
+ const bus = createBus();
188
+
126
189
  wireApiFromManifest(manifest, {
127
190
  handler,
128
191
  firebaseProjectId: process.env.FIREBASE_PROJECT_ID!,
129
192
  registerRoute,
130
193
  ensureJwtAuthorizer,
194
+ buses: [bus],
131
195
  });
132
196
 
133
197
  return { ApiUrl: api.url };
@@ -137,9 +201,6 @@ export default $config({
137
201
 
138
202
  Swap in `restApiAdapter` if you prefer API Gateway REST APIs—the wiring contract is identical.
139
203
 
140
- > Tip
141
- > Set `FIREBASE_PROJECT_ID` in your environment when using `@FirebaseAuth()` so the JWT authorizer is configured correctly.
142
-
143
204
  ## Publishing
144
205
 
145
206
  ```bash
@@ -148,8 +209,6 @@ npm version patch
148
209
  pnpm run release
149
210
  ```
150
211
 
151
- The release script builds the ESM/CJS bundles via `tsup` before publishing.
152
-
153
212
  ## License
154
213
 
155
214
  MIT
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/bus/index.ts
21
+ var bus_exports = {};
22
+ __export(bus_exports, {
23
+ On: () => On,
24
+ publish: () => publish
25
+ });
26
+ module.exports = __toCommonJS(bus_exports);
27
+
28
+ // src/core/handler.ts
29
+ function resolveHandler(target, propertyKey, descriptor) {
30
+ if (descriptor?.value && typeof descriptor.value === "function") {
31
+ return descriptor.value;
32
+ }
33
+ if (typeof target === "function" && propertyKey === void 0) {
34
+ return target;
35
+ }
36
+ if (target && propertyKey && typeof target[propertyKey] === "function") {
37
+ return target[propertyKey];
38
+ }
39
+ throw new Error("Unable to determine decorated function. Ensure decorators are applied to functions.");
40
+ }
41
+
42
+ // src/core/registry.ts
43
+ var eventMeta = /* @__PURE__ */ new Map();
44
+ function registerEvent(target, event) {
45
+ const handler = target;
46
+ const list = eventMeta.get(handler) ?? [];
47
+ list.push({ event });
48
+ eventMeta.set(handler, list);
49
+ }
50
+
51
+ // src/bus/decorators.ts
52
+ function On(event) {
53
+ return (target, propertyKey, descriptor) => {
54
+ if (!event) {
55
+ throw new Error("@On() requires an event name.");
56
+ }
57
+ const handler = resolveHandler(target, propertyKey, descriptor);
58
+ registerEvent(handler, event);
59
+ };
60
+ }
61
+
62
+ // src/bus/event-bus.ts
63
+ var import_node_crypto = require("crypto");
64
+ var AWS_TARGET = "AWSEvents.PutEvents";
65
+ var AWS_SERVICE = "events";
66
+ var DEFAULT_SOURCE = "sst-http";
67
+ async function publish(event, message) {
68
+ if (!event) {
69
+ throw new Error("publish() requires an event name.");
70
+ }
71
+ const payload = {
72
+ Entries: [
73
+ {
74
+ EventBusName: "default",
75
+ Source: DEFAULT_SOURCE,
76
+ DetailType: event,
77
+ Detail: JSON.stringify(message ?? null)
78
+ }
79
+ ]
80
+ };
81
+ console.log(payload);
82
+ await putEventsViaFetch(payload);
83
+ }
84
+ async function putEventsViaFetch(payload) {
85
+ const region = resolveRegion();
86
+ const creds = resolveCredentials();
87
+ const host = `events.${region}.amazonaws.com`;
88
+ const url = `https://${host}/`;
89
+ const body = JSON.stringify(payload);
90
+ const amzDate = toAmzDate(/* @__PURE__ */ new Date());
91
+ const dateStamp = amzDate.slice(0, 8);
92
+ const headers = {
93
+ "content-type": "application/x-amz-json-1.1",
94
+ "x-amz-date": amzDate,
95
+ "x-amz-target": AWS_TARGET,
96
+ host
97
+ };
98
+ if (creds.sessionToken) {
99
+ headers["x-amz-security-token"] = creds.sessionToken;
100
+ }
101
+ const signedHeaders = getSignedHeaders(headers);
102
+ const canonicalRequest = [
103
+ "POST",
104
+ "/",
105
+ "",
106
+ canonicalizeHeaders(headers),
107
+ signedHeaders,
108
+ sha256(body)
109
+ ].join("\n");
110
+ const scope = `${dateStamp}/${region}/${AWS_SERVICE}/aws4_request`;
111
+ const stringToSign = [
112
+ "AWS4-HMAC-SHA256",
113
+ amzDate,
114
+ scope,
115
+ sha256(canonicalRequest)
116
+ ].join("\n");
117
+ const signingKey = getSigningKey(creds.secretAccessKey, dateStamp, region, AWS_SERVICE);
118
+ const signature = hmac(signingKey, stringToSign);
119
+ const authorization = `AWS4-HMAC-SHA256 Credential=${creds.accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
120
+ const response = await fetch(url, {
121
+ method: "POST",
122
+ headers: {
123
+ ...headers,
124
+ Authorization: authorization
125
+ },
126
+ body
127
+ });
128
+ if (!response.ok) {
129
+ const text = await response.text();
130
+ throw new Error(`EventBridge PutEvents failed: ${response.status} ${text}`);
131
+ }
132
+ }
133
+ function resolveRegion() {
134
+ const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
135
+ if (!region) {
136
+ throw new Error("AWS region is not set");
137
+ }
138
+ return region;
139
+ }
140
+ function resolveCredentials() {
141
+ const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
142
+ const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
143
+ const sessionToken = process.env.AWS_SESSION_TOKEN;
144
+ if (!accessKeyId || !secretAccessKey) {
145
+ throw new Error("AWS credentials are not set");
146
+ }
147
+ return { accessKeyId, secretAccessKey, sessionToken };
148
+ }
149
+ function toAmzDate(date) {
150
+ const pad = (value) => value.toString().padStart(2, "0");
151
+ return [
152
+ date.getUTCFullYear(),
153
+ pad(date.getUTCMonth() + 1),
154
+ pad(date.getUTCDate()),
155
+ "T",
156
+ pad(date.getUTCHours()),
157
+ pad(date.getUTCMinutes()),
158
+ pad(date.getUTCSeconds()),
159
+ "Z"
160
+ ].join("");
161
+ }
162
+ function sha256(value) {
163
+ return (0, import_node_crypto.createHash)("sha256").update(value, "utf8").digest("hex");
164
+ }
165
+ function hmac(key, value) {
166
+ return (0, import_node_crypto.createHmac)("sha256", key).update(value, "utf8").digest("hex");
167
+ }
168
+ function getSigningKey(secret, date, region, service) {
169
+ const kDate = (0, import_node_crypto.createHmac)("sha256", `AWS4${secret}`).update(date, "utf8").digest();
170
+ const kRegion = (0, import_node_crypto.createHmac)("sha256", kDate).update(region, "utf8").digest();
171
+ const kService = (0, import_node_crypto.createHmac)("sha256", kRegion).update(service, "utf8").digest();
172
+ return (0, import_node_crypto.createHmac)("sha256", kService).update("aws4_request", "utf8").digest();
173
+ }
174
+ function canonicalizeHeaders(headers) {
175
+ return Object.keys(headers).map((key) => key.toLowerCase()).sort().map((key) => `${key}:${headers[key].trim()}
176
+ `).join("");
177
+ }
178
+ function getSignedHeaders(headers) {
179
+ return Object.keys(headers).map((key) => key.toLowerCase()).sort().join(";");
180
+ }
181
+ // Annotate the CommonJS export names for ESM import in node:
182
+ 0 && (module.exports = {
183
+ On,
184
+ publish
185
+ });
@@ -0,0 +1,7 @@
1
+ import { L as LegacyDecorator } from '../handler-DaM4Racx.cjs';
2
+
3
+ declare function On(event: string): LegacyDecorator;
4
+
5
+ declare function publish(event: string, message: unknown): Promise<void>;
6
+
7
+ export { On, publish };
@@ -0,0 +1,7 @@
1
+ import { L as LegacyDecorator } from '../handler-DaM4Racx.js';
2
+
3
+ declare function On(event: string): LegacyDecorator;
4
+
5
+ declare function publish(event: string, message: unknown): Promise<void>;
6
+
7
+ export { On, publish };
@@ -0,0 +1,9 @@
1
+ import {
2
+ On,
3
+ publish
4
+ } from "../chunk-LLR3DQ65.js";
5
+ import "../chunk-YMGEGOSD.js";
6
+ export {
7
+ On,
8
+ publish
9
+ };
@@ -1,4 +1,4 @@
1
- // src/paths.ts
1
+ // src/http/paths.ts
2
2
  var API_GATEWAY_PARAM_RE = /(^|\/):([A-Za-z0-9_]+(?:[+*])?)(?=\/|$)/g;
3
3
  function normalizeRouterPath(path) {
4
4
  return path.replace(/\{([^/{}]+)\}/g, ":$1");
@@ -0,0 +1,140 @@
1
+ import {
2
+ registerEvent,
3
+ resolveHandler
4
+ } from "./chunk-YMGEGOSD.js";
5
+
6
+ // src/bus/decorators.ts
7
+ function On(event) {
8
+ return (target, propertyKey, descriptor) => {
9
+ if (!event) {
10
+ throw new Error("@On() requires an event name.");
11
+ }
12
+ const handler = resolveHandler(target, propertyKey, descriptor);
13
+ registerEvent(handler, event);
14
+ };
15
+ }
16
+
17
+ // src/bus/event-bus.ts
18
+ import { createHash, createHmac } from "crypto";
19
+ var AWS_TARGET = "AWSEvents.PutEvents";
20
+ var AWS_SERVICE = "events";
21
+ var DEFAULT_SOURCE = "sst-http";
22
+ async function publish(event, message) {
23
+ if (!event) {
24
+ throw new Error("publish() requires an event name.");
25
+ }
26
+ const payload = {
27
+ Entries: [
28
+ {
29
+ EventBusName: "default",
30
+ Source: DEFAULT_SOURCE,
31
+ DetailType: event,
32
+ Detail: JSON.stringify(message ?? null)
33
+ }
34
+ ]
35
+ };
36
+ console.log(payload);
37
+ await putEventsViaFetch(payload);
38
+ }
39
+ async function putEventsViaFetch(payload) {
40
+ const region = resolveRegion();
41
+ const creds = resolveCredentials();
42
+ const host = `events.${region}.amazonaws.com`;
43
+ const url = `https://${host}/`;
44
+ const body = JSON.stringify(payload);
45
+ const amzDate = toAmzDate(/* @__PURE__ */ new Date());
46
+ const dateStamp = amzDate.slice(0, 8);
47
+ const headers = {
48
+ "content-type": "application/x-amz-json-1.1",
49
+ "x-amz-date": amzDate,
50
+ "x-amz-target": AWS_TARGET,
51
+ host
52
+ };
53
+ if (creds.sessionToken) {
54
+ headers["x-amz-security-token"] = creds.sessionToken;
55
+ }
56
+ const signedHeaders = getSignedHeaders(headers);
57
+ const canonicalRequest = [
58
+ "POST",
59
+ "/",
60
+ "",
61
+ canonicalizeHeaders(headers),
62
+ signedHeaders,
63
+ sha256(body)
64
+ ].join("\n");
65
+ const scope = `${dateStamp}/${region}/${AWS_SERVICE}/aws4_request`;
66
+ const stringToSign = [
67
+ "AWS4-HMAC-SHA256",
68
+ amzDate,
69
+ scope,
70
+ sha256(canonicalRequest)
71
+ ].join("\n");
72
+ const signingKey = getSigningKey(creds.secretAccessKey, dateStamp, region, AWS_SERVICE);
73
+ const signature = hmac(signingKey, stringToSign);
74
+ const authorization = `AWS4-HMAC-SHA256 Credential=${creds.accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
75
+ const response = await fetch(url, {
76
+ method: "POST",
77
+ headers: {
78
+ ...headers,
79
+ Authorization: authorization
80
+ },
81
+ body
82
+ });
83
+ if (!response.ok) {
84
+ const text = await response.text();
85
+ throw new Error(`EventBridge PutEvents failed: ${response.status} ${text}`);
86
+ }
87
+ }
88
+ function resolveRegion() {
89
+ const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
90
+ if (!region) {
91
+ throw new Error("AWS region is not set");
92
+ }
93
+ return region;
94
+ }
95
+ function resolveCredentials() {
96
+ const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
97
+ const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
98
+ const sessionToken = process.env.AWS_SESSION_TOKEN;
99
+ if (!accessKeyId || !secretAccessKey) {
100
+ throw new Error("AWS credentials are not set");
101
+ }
102
+ return { accessKeyId, secretAccessKey, sessionToken };
103
+ }
104
+ function toAmzDate(date) {
105
+ const pad = (value) => value.toString().padStart(2, "0");
106
+ return [
107
+ date.getUTCFullYear(),
108
+ pad(date.getUTCMonth() + 1),
109
+ pad(date.getUTCDate()),
110
+ "T",
111
+ pad(date.getUTCHours()),
112
+ pad(date.getUTCMinutes()),
113
+ pad(date.getUTCSeconds()),
114
+ "Z"
115
+ ].join("");
116
+ }
117
+ function sha256(value) {
118
+ return createHash("sha256").update(value, "utf8").digest("hex");
119
+ }
120
+ function hmac(key, value) {
121
+ return createHmac("sha256", key).update(value, "utf8").digest("hex");
122
+ }
123
+ function getSigningKey(secret, date, region, service) {
124
+ const kDate = createHmac("sha256", `AWS4${secret}`).update(date, "utf8").digest();
125
+ const kRegion = createHmac("sha256", kDate).update(region, "utf8").digest();
126
+ const kService = createHmac("sha256", kRegion).update(service, "utf8").digest();
127
+ return createHmac("sha256", kService).update("aws4_request", "utf8").digest();
128
+ }
129
+ function canonicalizeHeaders(headers) {
130
+ return Object.keys(headers).map((key) => key.toLowerCase()).sort().map((key) => `${key}:${headers[key].trim()}
131
+ `).join("");
132
+ }
133
+ function getSignedHeaders(headers) {
134
+ return Object.keys(headers).map((key) => key.toLowerCase()).sort().join(";");
135
+ }
136
+
137
+ export {
138
+ On,
139
+ publish
140
+ };