sst-http 1.3.2 → 1.3.3-beta.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/dist/infra.js CHANGED
@@ -1,15 +1,37 @@
1
1
  import {
2
2
  normalizeApiGatewayPath
3
- } from "./chunk-5MOJ3SW6.js";
3
+ } from "./chunk-5OUNKYO5.js";
4
4
 
5
5
  // src/infra.ts
6
6
  import { readFileSync } from "fs";
7
7
  import { resolve } from "path";
8
- function ensureSstAws(source) {
9
- if (source?.sst?.aws) {
10
- return source.sst.aws;
8
+
9
+ // src/core/infra.ts
10
+ function isRecord(value) {
11
+ return typeof value === "object" && value !== null;
12
+ }
13
+ function getFunction(value, key) {
14
+ if (!isRecord(value)) {
15
+ return void 0;
16
+ }
17
+ const candidate = value[key];
18
+ return typeof candidate === "function" ? candidate : void 0;
19
+ }
20
+ function getStringProp(value, key) {
21
+ if (!isRecord(value)) {
22
+ return void 0;
11
23
  }
12
- const aws = globalThis.sst?.aws;
24
+ const candidate = value[key];
25
+ return typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
26
+ }
27
+ function ensureRecord(value, message) {
28
+ if (!isRecord(value)) {
29
+ throw new Error(message);
30
+ }
31
+ return value;
32
+ }
33
+ function ensureSstAws(source) {
34
+ const aws = typeof sst !== "undefined" ? sst.aws : source?.sst?.aws ?? globalThis.sst?.aws;
13
35
  if (!aws) {
14
36
  throw new Error(
15
37
  "SST aws namespace is not available. Ensure this code runs within an SST config."
@@ -17,32 +39,127 @@ function ensureSstAws(source) {
17
39
  }
18
40
  return aws;
19
41
  }
20
- function wireApiFromManifest(manifest, opts) {
21
- if (!manifest || !Array.isArray(manifest.routes)) {
22
- throw new Error("Invalid routes manifest");
42
+ function resolveHandlerInput(handler) {
43
+ if (handler === void 0) {
44
+ return void 0;
23
45
  }
24
- const firebaseRoutes = manifest.routes.filter((route) => route.auth.type === "firebase");
25
- let firebaseAuthorizerRef;
26
- if (firebaseRoutes.length > 0) {
27
- if (!opts.firebaseProjectId) {
28
- throw new Error("firebaseProjectId is required when using @FirebaseAuth()");
46
+ if (typeof handler === "string") {
47
+ return handler;
48
+ }
49
+ if (!isRecord(handler)) {
50
+ throw new Error("Unsupported handler type: provide a handler string, FunctionArgs, or a Function ARN/output");
51
+ }
52
+ if ("arn" in handler) {
53
+ return handler.arn;
54
+ }
55
+ if (typeof handler.handler === "string") {
56
+ return handler;
57
+ }
58
+ throw new Error("Unsupported handler type: provide a handler string, FunctionArgs, or a Function ARN/output");
59
+ }
60
+
61
+ // src/bus/infra.ts
62
+ var MAX_SUBSCRIBER_NAME_LENGTH = 60;
63
+ function createBus() {
64
+ const aws = ensureSstAws();
65
+ return new aws.Bus("default");
66
+ }
67
+ function getBus() {
68
+ const aws = ensureSstAws();
69
+ return aws.Bus.get("default", "default");
70
+ }
71
+ function wireEventsFromManifest(events, opts) {
72
+ if (!events || events.length === 0) {
73
+ return;
74
+ }
75
+ const aws = ensureSstAws(opts.source);
76
+ const busMap = normalizeBusInput(opts.buses);
77
+ const subscriber = resolveHandlerInput(opts.handler);
78
+ const seen = /* @__PURE__ */ new Set();
79
+ for (const event of events) {
80
+ const key = `default:${event.event}`;
81
+ if (seen.has(key)) {
82
+ continue;
29
83
  }
30
- const issuer = `https://securetoken.google.com/${opts.firebaseProjectId}`;
31
- firebaseAuthorizerRef = opts.ensureJwtAuthorizer("firebase", {
32
- issuer,
33
- audiences: [opts.firebaseProjectId]
34
- });
84
+ seen.add(key);
85
+ const bus = resolveBusForEvent(busMap, aws);
86
+ const subscriberName = buildSubscriberName(event.event);
87
+ subscribeToBus(bus, subscriberName, subscriber, event.event);
88
+ }
89
+ }
90
+ function normalizeBusInput(input) {
91
+ if (!input) {
92
+ return void 0;
35
93
  }
94
+ const map = /* @__PURE__ */ new Map();
95
+ if (Array.isArray(input)) {
96
+ for (const bus of input) {
97
+ const key2 = getBusKey(bus);
98
+ if (key2) {
99
+ map.set(key2, bus);
100
+ }
101
+ }
102
+ if (map.size === 1 && !map.has("default")) {
103
+ const [bus] = map.values();
104
+ map.set("default", bus);
105
+ }
106
+ return map;
107
+ }
108
+ if (isBusRecord(input)) {
109
+ for (const [key2, bus] of Object.entries(input)) {
110
+ map.set(key2, bus);
111
+ }
112
+ return map;
113
+ }
114
+ const key = getBusKey(input) ?? "default";
115
+ map.set(key, input);
116
+ return map;
117
+ }
118
+ function isBusRecord(input) {
119
+ return !Array.isArray(input) && isRecord(input) && !("subscribe" in input);
120
+ }
121
+ function getBusKey(bus) {
122
+ return getStringProp(bus, "name") ?? getStringProp(bus, "constructorName");
123
+ }
124
+ function resolveBusForEvent(busMap, aws) {
125
+ if (busMap && busMap.size > 0) {
126
+ const direct = busMap.get("default");
127
+ if (direct) {
128
+ return direct;
129
+ }
130
+ if (busMap.size === 1) {
131
+ return busMap.values().next().value;
132
+ }
133
+ }
134
+ return aws.Bus.get("default", "default");
135
+ }
136
+ function subscribeToBus(bus, subscriberName, subscriber, eventName) {
137
+ if (typeof bus.subscribe !== "function") {
138
+ throw new Error("Bus instance does not support subscribe().");
139
+ }
140
+ bus.subscribe(subscriberName, subscriber, {
141
+ pattern: {
142
+ detailType: [eventName]
143
+ }
144
+ });
145
+ }
146
+ function buildSubscriberName(eventName) {
147
+ const base = `default-${eventName}`.replace(/[^a-zA-Z0-9]/g, "");
148
+ if (base.length === 0) {
149
+ return "Event";
150
+ }
151
+ return base.length > MAX_SUBSCRIBER_NAME_LENGTH ? base.slice(0, MAX_SUBSCRIBER_NAME_LENGTH) : base;
152
+ }
153
+
154
+ // src/http/infra.ts
155
+ function wireRoutesFromManifest(manifest, opts) {
156
+ const firebaseRoutes = manifest.routes.filter((route) => route.auth.type === "firebase");
157
+ const firebaseAuthorizerRef = ensureFirebaseAuthorizer(firebaseRoutes.length, opts);
36
158
  for (const route of manifest.routes) {
37
159
  const isProtected = route.auth.type === "firebase";
38
160
  const rawPath = route.path.startsWith("/") ? route.path : `/${route.path}`;
39
161
  const path = normalizeApiGatewayPath(rawPath);
40
- const authConfig = isProtected && route.auth.type === "firebase" ? {
41
- name: "firebase",
42
- optional: route.auth.optional,
43
- roles: route.auth.roles,
44
- ref: firebaseAuthorizerRef
45
- } : void 0;
162
+ const authConfig = buildAuthorizerConfig(route, firebaseAuthorizerRef);
46
163
  opts.registerRoute(route.method, path, {
47
164
  handler: opts.handler,
48
165
  protected: isProtected,
@@ -50,33 +167,74 @@ function wireApiFromManifest(manifest, opts) {
50
167
  });
51
168
  }
52
169
  }
53
- function loadRoutesManifest(filePath) {
54
- const resolved = resolve(filePath);
55
- const contents = readFileSync(resolved, "utf8");
56
- const manifest = JSON.parse(contents);
57
- if (!manifest || !Array.isArray(manifest.routes)) {
58
- throw new Error(`Invalid routes manifest at ${resolved}`);
59
- }
60
- return manifest;
61
- }
62
170
  function httpApiAdapter(args) {
63
171
  const aws = args?.api ? void 0 : ensureSstAws(args);
64
172
  const api = args?.api ?? new aws.ApiGatewayV2(args?.apiName ?? "HttpApi", args?.apiArgs);
173
+ const ensureJwtAuthorizer = createHttpAuthorizerManager(api);
174
+ const registerRoute = createRouteRegistrar(api, "ApiGatewayV2");
175
+ return {
176
+ api,
177
+ registerRoute,
178
+ ensureJwtAuthorizer
179
+ };
180
+ }
181
+ function restApiAdapter(args) {
182
+ const aws = args?.api ? void 0 : ensureSstAws(args);
183
+ const api = args?.api ?? new aws.ApiGateway(args?.apiName ?? "RestApi", args?.apiArgs);
184
+ const ensureJwtAuthorizer = createRestAuthorizerManager(api);
185
+ const registerRoute = createRouteRegistrar(api, "ApiGateway");
186
+ return {
187
+ api,
188
+ registerRoute,
189
+ ensureJwtAuthorizer
190
+ };
191
+ }
192
+ function ensureFirebaseAuthorizer(firebaseRouteCount, opts) {
193
+ if (firebaseRouteCount === 0) {
194
+ return void 0;
195
+ }
196
+ if (!opts.firebaseProjectId) {
197
+ throw new Error("firebaseProjectId is required when using @FirebaseAuth()");
198
+ }
199
+ const issuer = `https://securetoken.google.com/${opts.firebaseProjectId}`;
200
+ return opts.ensureJwtAuthorizer("firebase", {
201
+ issuer,
202
+ audiences: [opts.firebaseProjectId]
203
+ });
204
+ }
205
+ function buildAuthorizerConfig(route, firebaseAuthorizerRef) {
206
+ if (route.auth.type !== "firebase") {
207
+ return void 0;
208
+ }
209
+ return {
210
+ name: "firebase",
211
+ optional: route.auth.optional,
212
+ roles: route.auth.roles,
213
+ ref: firebaseAuthorizerRef
214
+ };
215
+ }
216
+ function createHttpAuthorizerManager(api) {
65
217
  const authorizers = /* @__PURE__ */ new Map();
66
- const ensureJwtAuthorizer = (name, cfg) => {
218
+ return (name, cfg) => {
67
219
  if (authorizers.has(name)) {
68
220
  return authorizers.get(name);
69
221
  }
70
- const apiAny = api;
222
+ const addAuthorizer = getFunction(api, "addAuthorizer");
223
+ const addAuthorizers = getFunction(api, "addAuthorizers");
224
+ const authorizer = getFunction(api, "authorizer");
71
225
  let ref = void 0;
72
- if (typeof apiAny["addAuthorizer"] === "function") {
73
- const created = apiAny.addAuthorizer({
226
+ if (addAuthorizer) {
227
+ const created = addAuthorizer.call(api, {
74
228
  name,
75
229
  jwt: { issuer: cfg.issuer, audiences: cfg.audiences }
76
230
  });
77
- ref = created.id;
78
- } else if (typeof apiAny.addAuthorizers === "function") {
79
- apiAny.addAuthorizers({
231
+ if (isRecord(created) && "id" in created) {
232
+ ref = created.id ?? created;
233
+ } else {
234
+ ref = created;
235
+ }
236
+ } else if (addAuthorizers) {
237
+ addAuthorizers.call(api, {
80
238
  [name]: {
81
239
  type: "jwt",
82
240
  jwt: {
@@ -86,8 +244,8 @@ function httpApiAdapter(args) {
86
244
  }
87
245
  });
88
246
  ref = name;
89
- } else if (typeof apiAny.authorizer === "function") {
90
- ref = apiAny.authorizer(name, {
247
+ } else if (authorizer) {
248
+ ref = authorizer.call(api, name, {
91
249
  type: "jwt",
92
250
  jwt: {
93
251
  issuer: cfg.issuer,
@@ -100,58 +258,13 @@ function httpApiAdapter(args) {
100
258
  authorizers.set(name, ref);
101
259
  return ref;
102
260
  };
103
- const registerRoute = (method, path, config) => {
104
- const apiAny = api;
105
- const normalizedPath = normalizeApiGatewayPath(path);
106
- const routeKey = `${method} ${normalizedPath}`;
107
- const asAny = config.handler;
108
- const handlerInput = typeof asAny === "string" ? asAny : asAny && typeof asAny.arn !== "undefined" ? asAny.arn : asAny && typeof asAny.handler === "string" ? asAny.handler : asAny === void 0 ? void 0 : (() => {
109
- throw new Error("Unsupported handler type: provide a handler string, FunctionArgs, or a Function ARN/output");
110
- })();
111
- const args2 = {};
112
- if (config.protected && config.authorizer) {
113
- args2.auth = {
114
- jwt: {
115
- authorizer: config.authorizer.ref ?? config.authorizer.name,
116
- scopes: config.authorizer.roles
117
- }
118
- };
119
- }
120
- if (typeof apiAny.route === "function") {
121
- apiAny.route(routeKey, handlerInput, args2);
122
- return;
123
- }
124
- if (typeof apiAny.addRoutes === "function") {
125
- apiAny.addRoutes({
126
- [routeKey]: {
127
- handler: handlerInput,
128
- ...args2
129
- }
130
- });
131
- return;
132
- }
133
- if (typeof apiAny.addRoute === "function") {
134
- apiAny.addRoute(routeKey, { handler: handlerInput, ...args2 });
135
- return;
136
- }
137
- throw new Error("Unsupported ApiGatewayV2 instance: expected route() or addRoutes() method.");
138
- };
139
- return {
140
- api,
141
- registerRoute,
142
- ensureJwtAuthorizer
143
- };
144
261
  }
145
- function restApiAdapter(args) {
146
- const aws = args?.api ? void 0 : ensureSstAws(args);
147
- const api = args?.api ?? new aws.ApiGateway(args?.apiName ?? "RestApi", args?.apiArgs);
262
+ function createRestAuthorizerManager(api) {
148
263
  const authorizers = /* @__PURE__ */ new Map();
149
- const ensureJwtAuthorizer = (name, cfg) => {
264
+ return (name, cfg) => {
150
265
  if (authorizers.has(name)) {
151
266
  return authorizers.get(name);
152
267
  }
153
- const apiAny = api;
154
- let ref = name;
155
268
  const payload = {
156
269
  type: "jwt",
157
270
  jwt: {
@@ -159,64 +272,98 @@ function restApiAdapter(args) {
159
272
  audience: cfg.audiences
160
273
  }
161
274
  };
162
- if (typeof apiAny.addAuthorizers === "function") {
163
- apiAny.addAuthorizers({
275
+ const addAuthorizers = getFunction(api, "addAuthorizers");
276
+ const authorizer = getFunction(api, "authorizer");
277
+ let ref = name;
278
+ if (addAuthorizers) {
279
+ addAuthorizers.call(api, {
164
280
  [name]: payload
165
281
  });
166
- } else if (typeof apiAny.authorizer === "function") {
167
- ref = apiAny.authorizer(name, payload);
282
+ } else if (authorizer) {
283
+ ref = authorizer.call(api, name, payload);
168
284
  } else {
169
- apiAny.authorizers = {
170
- ...apiAny.authorizers ?? {},
285
+ const apiRecord = ensureRecord(api, "ApiGateway instance does not support authorizers.");
286
+ const current = isRecord(apiRecord.authorizers) ? apiRecord.authorizers : {};
287
+ apiRecord.authorizers = {
288
+ ...current,
171
289
  [name]: payload
172
290
  };
173
291
  }
174
292
  authorizers.set(name, ref);
175
293
  return ref;
176
294
  };
177
- const registerRoute = (method, path, config) => {
178
- const apiAny = api;
295
+ }
296
+ function createRouteRegistrar(api, apiLabel) {
297
+ return (method, path, config) => {
179
298
  const normalizedPath = normalizeApiGatewayPath(path);
180
299
  const routeKey = `${method} ${normalizedPath}`;
181
- const asAny = config.handler;
182
- const handlerInput = typeof asAny === "string" ? asAny : asAny && typeof asAny.arn !== "undefined" ? asAny.arn : asAny && typeof asAny.handler === "string" ? asAny.handler : asAny === void 0 ? void 0 : (() => {
183
- throw new Error("Unsupported handler type: provide a handler string, FunctionArgs, or a Function ARN/output");
184
- })();
185
- const args2 = {};
186
- if (config.protected && config.authorizer) {
187
- args2.auth = {
188
- jwt: {
189
- authorizer: config.authorizer.ref ?? config.authorizer.name,
190
- scopes: config.authorizer.roles
191
- }
192
- };
193
- }
194
- if (typeof apiAny.route === "function") {
195
- apiAny.route(routeKey, handlerInput, args2);
300
+ const handlerInput = resolveHandlerInput(config.handler);
301
+ const args = buildRouteArgs(config);
302
+ const route = getFunction(api, "route");
303
+ if (route) {
304
+ route.call(api, routeKey, handlerInput, args);
196
305
  return;
197
306
  }
198
- if (typeof apiAny.addRoutes === "function") {
199
- apiAny.addRoutes({
307
+ const addRoutes = getFunction(api, "addRoutes");
308
+ if (addRoutes) {
309
+ addRoutes.call(api, {
200
310
  [routeKey]: {
201
311
  handler: handlerInput,
202
- ...args2
312
+ ...args
203
313
  }
204
314
  });
205
315
  return;
206
316
  }
207
- if (typeof apiAny.addRoute === "function") {
208
- apiAny.addRoute(routeKey, { handler: handlerInput, ...args2 });
317
+ const addRoute = getFunction(api, "addRoute");
318
+ if (addRoute) {
319
+ addRoute.call(api, routeKey, { handler: handlerInput, ...args });
209
320
  return;
210
321
  }
211
- throw new Error("Unsupported ApiGateway instance: expected route() or addRoutes() method.");
322
+ throw new Error(`Unsupported ${apiLabel} instance: expected route() or addRoutes() method.`);
212
323
  };
324
+ }
325
+ function buildRouteArgs(config) {
326
+ if (!config.protected || !config.authorizer) {
327
+ return {};
328
+ }
213
329
  return {
214
- api,
215
- registerRoute,
216
- ensureJwtAuthorizer
330
+ auth: {
331
+ jwt: {
332
+ authorizer: config.authorizer.ref ?? config.authorizer.name,
333
+ scopes: config.authorizer.roles
334
+ }
335
+ }
217
336
  };
218
337
  }
338
+
339
+ // src/infra.ts
340
+ function wireApiFromManifest(manifest, opts) {
341
+ if (!manifest || !Array.isArray(manifest.routes)) {
342
+ throw new Error("Invalid routes manifest");
343
+ }
344
+ wireRoutesFromManifest(manifest, {
345
+ handler: opts.handler,
346
+ firebaseProjectId: opts.firebaseProjectId,
347
+ registerRoute: opts.registerRoute,
348
+ ensureJwtAuthorizer: opts.ensureJwtAuthorizer
349
+ });
350
+ wireEventsFromManifest(manifest.events, {
351
+ handler: opts.handler,
352
+ buses: opts.buses
353
+ });
354
+ }
355
+ function loadRoutesManifest(filePath) {
356
+ const resolved = resolve(filePath);
357
+ const contents = readFileSync(resolved, "utf8");
358
+ const manifest = JSON.parse(contents);
359
+ if (!manifest || !Array.isArray(manifest.routes)) {
360
+ throw new Error(`Invalid routes manifest at ${resolved}`);
361
+ }
362
+ return manifest;
363
+ }
219
364
  export {
365
+ createBus,
366
+ getBus,
220
367
  httpApiAdapter,
221
368
  loadRoutesManifest,
222
369
  restApiAdapter,
@@ -47,8 +47,12 @@ type RoutesManifestRoute = {
47
47
  path: string;
48
48
  auth: RoutesManifestAuth;
49
49
  };
50
+ type RoutesManifestEvent = {
51
+ event: string;
52
+ };
50
53
  type RoutesManifest = {
51
54
  routes: RoutesManifestRoute[];
55
+ events?: RoutesManifestEvent[];
52
56
  };
53
57
 
54
- export type { FirebaseAuthOptions as F, HttpMethod as H, RoutesManifest as R, RoutesManifestRoute as a, RoutesManifestAuth as b, ResponseLike as c, RouteOptions as d, Handler as e, HandlerContext as f, FirebaseAuthMetadata as g, FirebaseClaims as h };
58
+ export type { FirebaseAuthOptions as F, Handler as H, ResponseLike as R, HandlerContext as a, HttpMethod as b, FirebaseAuthMetadata as c, FirebaseClaims as d, RouteOptions as e, RoutesManifestEvent as f, RoutesManifest as g, RoutesManifestRoute as h, RoutesManifestAuth as i };
@@ -47,8 +47,12 @@ type RoutesManifestRoute = {
47
47
  path: string;
48
48
  auth: RoutesManifestAuth;
49
49
  };
50
+ type RoutesManifestEvent = {
51
+ event: string;
52
+ };
50
53
  type RoutesManifest = {
51
54
  routes: RoutesManifestRoute[];
55
+ events?: RoutesManifestEvent[];
52
56
  };
53
57
 
54
- export type { FirebaseAuthOptions as F, HttpMethod as H, RoutesManifest as R, RoutesManifestRoute as a, RoutesManifestAuth as b, ResponseLike as c, RouteOptions as d, Handler as e, HandlerContext as f, FirebaseAuthMetadata as g, FirebaseClaims as h };
58
+ export type { FirebaseAuthOptions as F, Handler as H, ResponseLike as R, HandlerContext as a, HttpMethod as b, FirebaseAuthMetadata as c, FirebaseClaims as d, RouteOptions as e, RoutesManifestEvent as f, RoutesManifest as g, RoutesManifestRoute as h, RoutesManifestAuth as i };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sst-http",
3
- "version": "1.3.2",
3
+ "version": "1.3.3-beta.2",
4
4
  "description": "Decorator-based routing for SST v3 with a single Lambda and Firebase JWT authorizer.",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -26,6 +26,16 @@
26
26
  "types": "./dist/infra.d.ts",
27
27
  "import": "./dist/infra.js",
28
28
  "require": "./dist/infra.cjs"
29
+ },
30
+ "./bus": {
31
+ "types": "./dist/bus/index.d.ts",
32
+ "import": "./dist/bus/index.js",
33
+ "require": "./dist/bus/index.cjs"
34
+ },
35
+ "./http": {
36
+ "types": "./dist/http/index.d.ts",
37
+ "import": "./dist/http/index.js",
38
+ "require": "./dist/http/index.cjs"
29
39
  }
30
40
  },
31
41
  "bin": {
@@ -64,17 +74,18 @@
64
74
  "access": "public"
65
75
  },
66
76
  "scripts": {
67
- "build": "tsup src/index.ts src/infra.ts src/cli.ts --dts --format esm,cjs --clean",
68
- "dev": "tsup --watch",
77
+ "build": "tsup src/index.ts src/http/index.ts src/bus/index.ts src/infra.ts src/cli.ts --dts --format esm,cjs --clean",
78
+ "dev": "tsup src/index.ts src/http/index.ts src/bus/index.ts src/infra.ts src/cli.ts --dts --format esm,cjs --watch",
69
79
  "lint": "eslint .",
70
80
  "typecheck": "tsc --noEmit",
71
81
  "clean": "rm -rf dist",
72
82
  "prebuild": "pnpm clean",
73
83
  "prerelease": "pnpm lint && pnpm typecheck && pnpm build",
74
- "release": "pnpm publish --access public",
84
+ "release": "pnpm publish --access public --tag latest",
75
85
  "release:patch": "pnpm version patch && pnpm run release",
76
86
  "release:minor": "pnpm version minor && pnpm run release",
77
87
  "release:major": "pnpm version major && pnpm run release",
78
- "release:dry": "pnpm publish --access public --dry-run"
88
+ "release:dry": "pnpm publish --access public --dry-run",
89
+ "release:beta": "pnpm version prerelease --preid beta && pnpm publish --access public --tag beta"
79
90
  }
80
91
  }