@betterportal/framework 0.0.1

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.
Files changed (194) hide show
  1. package/README.md +13 -0
  2. package/lib/adapters/h3.d.ts +51 -0
  3. package/lib/adapters/h3.d.ts.map +1 -0
  4. package/lib/adapters/h3.js +1120 -0
  5. package/lib/adapters/h3.js.map +1 -0
  6. package/lib/codegen/cli.d.ts +3 -0
  7. package/lib/codegen/cli.d.ts.map +1 -0
  8. package/lib/codegen/cli.js +82 -0
  9. package/lib/codegen/cli.js.map +1 -0
  10. package/lib/codegen/emitter.d.ts +7 -0
  11. package/lib/codegen/emitter.d.ts.map +1 -0
  12. package/lib/codegen/emitter.js +453 -0
  13. package/lib/codegen/emitter.js.map +1 -0
  14. package/lib/codegen/init.d.ts +3 -0
  15. package/lib/codegen/init.d.ts.map +1 -0
  16. package/lib/codegen/init.js +90 -0
  17. package/lib/codegen/init.js.map +1 -0
  18. package/lib/codegen/scanner.d.ts +56 -0
  19. package/lib/codegen/scanner.d.ts.map +1 -0
  20. package/lib/codegen/scanner.js +484 -0
  21. package/lib/codegen/scanner.js.map +1 -0
  22. package/lib/codegen/validate.d.ts +14 -0
  23. package/lib/codegen/validate.d.ts.map +1 -0
  24. package/lib/codegen/validate.js +166 -0
  25. package/lib/codegen/validate.js.map +1 -0
  26. package/lib/contracts/auth.d.ts +160 -0
  27. package/lib/contracts/auth.d.ts.map +1 -0
  28. package/lib/contracts/auth.js +123 -0
  29. package/lib/contracts/auth.js.map +1 -0
  30. package/lib/contracts/binding.d.ts +169 -0
  31. package/lib/contracts/binding.d.ts.map +1 -0
  32. package/lib/contracts/binding.js +69 -0
  33. package/lib/contracts/binding.js.map +1 -0
  34. package/lib/contracts/common.d.ts +23 -0
  35. package/lib/contracts/common.d.ts.map +1 -0
  36. package/lib/contracts/common.js +18 -0
  37. package/lib/contracts/common.js.map +1 -0
  38. package/lib/contracts/config.d.ts +93 -0
  39. package/lib/contracts/config.d.ts.map +1 -0
  40. package/lib/contracts/config.js +62 -0
  41. package/lib/contracts/config.js.map +1 -0
  42. package/lib/contracts/controlPlane.d.ts +63 -0
  43. package/lib/contracts/controlPlane.d.ts.map +1 -0
  44. package/lib/contracts/controlPlane.js +2 -0
  45. package/lib/contracts/controlPlane.js.map +1 -0
  46. package/lib/contracts/json.d.ts +9 -0
  47. package/lib/contracts/json.d.ts.map +1 -0
  48. package/lib/contracts/json.js +6 -0
  49. package/lib/contracts/json.js.map +1 -0
  50. package/lib/contracts/manifest.d.ts +158 -0
  51. package/lib/contracts/manifest.d.ts.map +1 -0
  52. package/lib/contracts/manifest.js +40 -0
  53. package/lib/contracts/manifest.js.map +1 -0
  54. package/lib/contracts/observability.d.ts +77 -0
  55. package/lib/contracts/observability.d.ts.map +1 -0
  56. package/lib/contracts/observability.js +99 -0
  57. package/lib/contracts/observability.js.map +1 -0
  58. package/lib/contracts/platformConfig.d.ts +635 -0
  59. package/lib/contracts/platformConfig.d.ts.map +1 -0
  60. package/lib/contracts/platformConfig.js +256 -0
  61. package/lib/contracts/platformConfig.js.map +1 -0
  62. package/lib/contracts/registry.d.ts +104 -0
  63. package/lib/contracts/registry.d.ts.map +1 -0
  64. package/lib/contracts/registry.js +2 -0
  65. package/lib/contracts/registry.js.map +1 -0
  66. package/lib/contracts/route.d.ts +199 -0
  67. package/lib/contracts/route.d.ts.map +1 -0
  68. package/lib/contracts/route.js +26 -0
  69. package/lib/contracts/route.js.map +1 -0
  70. package/lib/contracts/serviceConfig.d.ts +88 -0
  71. package/lib/contracts/serviceConfig.d.ts.map +1 -0
  72. package/lib/contracts/serviceConfig.js +45 -0
  73. package/lib/contracts/serviceConfig.js.map +1 -0
  74. package/lib/contracts/streaming.d.ts +76 -0
  75. package/lib/contracts/streaming.d.ts.map +1 -0
  76. package/lib/contracts/streaming.js +31 -0
  77. package/lib/contracts/streaming.js.map +1 -0
  78. package/lib/contracts/view.d.ts +149 -0
  79. package/lib/contracts/view.d.ts.map +1 -0
  80. package/lib/contracts/view.js +82 -0
  81. package/lib/contracts/view.js.map +1 -0
  82. package/lib/controlPlane/store.d.ts +24 -0
  83. package/lib/controlPlane/store.d.ts.map +1 -0
  84. package/lib/controlPlane/store.js +70 -0
  85. package/lib/controlPlane/store.js.map +1 -0
  86. package/lib/controlPlane/sync.d.ts +8 -0
  87. package/lib/controlPlane/sync.d.ts.map +1 -0
  88. package/lib/controlPlane/sync.js +24 -0
  89. package/lib/controlPlane/sync.js.map +1 -0
  90. package/lib/controlPlane/types.d.ts +15 -0
  91. package/lib/controlPlane/types.d.ts.map +1 -0
  92. package/lib/controlPlane/types.js +2 -0
  93. package/lib/controlPlane/types.js.map +1 -0
  94. package/lib/index.d.ts +40 -0
  95. package/lib/index.d.ts.map +1 -0
  96. package/lib/index.js +40 -0
  97. package/lib/index.js.map +1 -0
  98. package/lib/runtime/auth/envelope.d.ts +32 -0
  99. package/lib/runtime/auth/envelope.d.ts.map +1 -0
  100. package/lib/runtime/auth/envelope.js +123 -0
  101. package/lib/runtime/auth/envelope.js.map +1 -0
  102. package/lib/runtime/auth/issuer.d.ts +44 -0
  103. package/lib/runtime/auth/issuer.d.ts.map +1 -0
  104. package/lib/runtime/auth/issuer.js +82 -0
  105. package/lib/runtime/auth/issuer.js.map +1 -0
  106. package/lib/runtime/auth/jwks.d.ts +7 -0
  107. package/lib/runtime/auth/jwks.d.ts.map +1 -0
  108. package/lib/runtime/auth/jwks.js +69 -0
  109. package/lib/runtime/auth/jwks.js.map +1 -0
  110. package/lib/runtime/auth/keypair.d.ts +21 -0
  111. package/lib/runtime/auth/keypair.d.ts.map +1 -0
  112. package/lib/runtime/auth/keypair.js +50 -0
  113. package/lib/runtime/auth/keypair.js.map +1 -0
  114. package/lib/runtime/auth/tokens.d.ts +25 -0
  115. package/lib/runtime/auth/tokens.d.ts.map +1 -0
  116. package/lib/runtime/auth/tokens.js +137 -0
  117. package/lib/runtime/auth/tokens.js.map +1 -0
  118. package/lib/runtime/auth/verifier.d.ts +45 -0
  119. package/lib/runtime/auth/verifier.d.ts.map +1 -0
  120. package/lib/runtime/auth/verifier.js +76 -0
  121. package/lib/runtime/auth/verifier.js.map +1 -0
  122. package/lib/runtime/bpHeaders.d.ts +10 -0
  123. package/lib/runtime/bpHeaders.d.ts.map +1 -0
  124. package/lib/runtime/bpHeaders.js +53 -0
  125. package/lib/runtime/bpHeaders.js.map +1 -0
  126. package/lib/runtime/configProvider.d.ts +41 -0
  127. package/lib/runtime/configProvider.d.ts.map +1 -0
  128. package/lib/runtime/configProvider.js +232 -0
  129. package/lib/runtime/configProvider.js.map +1 -0
  130. package/lib/runtime/configStore.d.ts +34 -0
  131. package/lib/runtime/configStore.d.ts.map +1 -0
  132. package/lib/runtime/configStore.js +197 -0
  133. package/lib/runtime/configStore.js.map +1 -0
  134. package/lib/runtime/configTicket.d.ts +49 -0
  135. package/lib/runtime/configTicket.d.ts.map +1 -0
  136. package/lib/runtime/configTicket.js +168 -0
  137. package/lib/runtime/configTicket.js.map +1 -0
  138. package/lib/runtime/h3.d.ts +28 -0
  139. package/lib/runtime/h3.d.ts.map +1 -0
  140. package/lib/runtime/h3.js +199 -0
  141. package/lib/runtime/h3.js.map +1 -0
  142. package/lib/runtime/handler.d.ts +55 -0
  143. package/lib/runtime/handler.d.ts.map +1 -0
  144. package/lib/runtime/handler.js +51 -0
  145. package/lib/runtime/handler.js.map +1 -0
  146. package/lib/runtime/http.d.ts +13 -0
  147. package/lib/runtime/http.d.ts.map +1 -0
  148. package/lib/runtime/http.js +114 -0
  149. package/lib/runtime/http.js.map +1 -0
  150. package/lib/runtime/jsonSchema.d.ts +4 -0
  151. package/lib/runtime/jsonSchema.d.ts.map +1 -0
  152. package/lib/runtime/jsonSchema.js +28 -0
  153. package/lib/runtime/jsonSchema.js.map +1 -0
  154. package/lib/runtime/manifest.d.ts +3 -0
  155. package/lib/runtime/manifest.d.ts.map +1 -0
  156. package/lib/runtime/manifest.js +5 -0
  157. package/lib/runtime/manifest.js.map +1 -0
  158. package/lib/runtime/media.d.ts +20 -0
  159. package/lib/runtime/media.d.ts.map +1 -0
  160. package/lib/runtime/media.js +70 -0
  161. package/lib/runtime/media.js.map +1 -0
  162. package/lib/runtime/registry.d.ts +67 -0
  163. package/lib/runtime/registry.d.ts.map +1 -0
  164. package/lib/runtime/registry.js +290 -0
  165. package/lib/runtime/registry.js.map +1 -0
  166. package/lib/runtime/serviceConfig.d.ts +38 -0
  167. package/lib/runtime/serviceConfig.d.ts.map +1 -0
  168. package/lib/runtime/serviceConfig.js +152 -0
  169. package/lib/runtime/serviceConfig.js.map +1 -0
  170. package/lib/runtime/statusViews.d.ts +23 -0
  171. package/lib/runtime/statusViews.d.ts.map +1 -0
  172. package/lib/runtime/statusViews.js +48 -0
  173. package/lib/runtime/statusViews.js.map +1 -0
  174. package/lib/runtime/stream.d.ts +41 -0
  175. package/lib/runtime/stream.d.ts.map +1 -0
  176. package/lib/runtime/stream.js +92 -0
  177. package/lib/runtime/stream.js.map +1 -0
  178. package/lib/runtime/streamHandler.d.ts +48 -0
  179. package/lib/runtime/streamHandler.d.ts.map +1 -0
  180. package/lib/runtime/streamHandler.js +49 -0
  181. package/lib/runtime/streamHandler.js.map +1 -0
  182. package/lib/runtime/tenantResolution.d.ts +4 -0
  183. package/lib/runtime/tenantResolution.d.ts.map +1 -0
  184. package/lib/runtime/tenantResolution.js +19 -0
  185. package/lib/runtime/tenantResolution.js.map +1 -0
  186. package/lib/runtime/uuid.d.ts +6 -0
  187. package/lib/runtime/uuid.d.ts.map +1 -0
  188. package/lib/runtime/uuid.js +27 -0
  189. package/lib/runtime/uuid.js.map +1 -0
  190. package/lib/runtime/view.d.ts +48 -0
  191. package/lib/runtime/view.d.ts.map +1 -0
  192. package/lib/runtime/view.js +111 -0
  193. package/lib/runtime/view.js.map +1 -0
  194. package/package.json +56 -0
@@ -0,0 +1,168 @@
1
+ import jwt from "jsonwebtoken";
2
+ import jwksClient from "jwks-rsa";
3
+ import { ServiceConfigTicketClaimsSchema } from "../contracts/serviceConfig.js";
4
+ import { uuidv7 } from "./uuid.js";
5
+ const ALLOWED_ALGORITHM = "RS256";
6
+ const ALLOWED_TYP = "JWT";
7
+ const KID_PATTERN = /^[A-Za-z0-9_-]+$/;
8
+ /** Audience every BetterPortal service-config ticket is minted for. */
9
+ export const CONFIG_TICKET_AUDIENCE = "betterportal-service-config";
10
+ /**
11
+ * Mint a config ticket as an RS256 JWT signed by the control plane's key.
12
+ * Replaces the legacy symmetric HMAC ticket - services verify it against the
13
+ * CP JWKS, so no shared secret is required and only the CP can issue tickets.
14
+ */
15
+ export function signServiceConfigTicket(options) {
16
+ const now = Math.floor(Date.now() / 1000);
17
+ const claims = ServiceConfigTicketClaimsSchema.parse({
18
+ iss: options.issuer,
19
+ aud: [CONFIG_TICKET_AUDIENCE],
20
+ sub: options.subject ?? "betterportal-control-plane",
21
+ exp: now + options.expiresInSeconds,
22
+ iat: now,
23
+ jti: options.jti ?? uuidv7(),
24
+ realm: "control-plane",
25
+ tenantId: options.tenantId,
26
+ serviceId: options.serviceId,
27
+ ...(options.bindingId ? { bindingId: options.bindingId } : {}),
28
+ actions: options.actions
29
+ });
30
+ return jwt.sign(claims, options.privateKeyPem, {
31
+ algorithm: ALLOWED_ALGORITHM,
32
+ keyid: options.kid,
33
+ header: { alg: ALLOWED_ALGORITHM, typ: ALLOWED_TYP, kid: options.kid }
34
+ });
35
+ }
36
+ const clientCache = new Map();
37
+ const CLIENT_CACHE_TTL_MS = 30 * 60 * 1000;
38
+ function getJwksClient(jwksUri, issuer) {
39
+ const cacheKey = `${issuer}|${jwksUri}`;
40
+ const now = Date.now();
41
+ const existing = clientCache.get(cacheKey);
42
+ if (existing && now - existing.lastUsed < CLIENT_CACHE_TTL_MS) {
43
+ existing.lastUsed = now;
44
+ return existing.client;
45
+ }
46
+ const client = jwksClient({
47
+ jwksUri,
48
+ cache: true,
49
+ cacheMaxAge: 10 * 60 * 1000,
50
+ rateLimit: true,
51
+ jwksRequestsPerMinute: 30,
52
+ timeout: 5000
53
+ });
54
+ clientCache.set(cacheKey, { client, lastUsed: now });
55
+ return client;
56
+ }
57
+ /**
58
+ * Verify a CP-signed config ticket against the CP JWKS. Throws on any failure.
59
+ * Pins RS256, rejects jku/x5u key references, and re-checks iss/aud/exp/serviceId
60
+ * after library verification (defence in depth).
61
+ */
62
+ export async function verifyServiceConfigTicket(token, options) {
63
+ if (typeof token !== "string" || token.length === 0) {
64
+ throw new Error("Config ticket is empty");
65
+ }
66
+ const parts = token.split(".");
67
+ if (parts.length !== 3) {
68
+ throw new Error("Config ticket must have exactly three parts");
69
+ }
70
+ const header = parseHeader(parts[0]);
71
+ if (header.alg !== ALLOWED_ALGORITHM) {
72
+ throw new Error(`Algorithm not allowed: ${String(header.alg)}`);
73
+ }
74
+ if (header.typ !== ALLOWED_TYP) {
75
+ throw new Error(`Ticket typ not allowed: ${String(header.typ)}`);
76
+ }
77
+ if ("jku" in header || "x5u" in header) {
78
+ throw new Error("Ticket header contains untrusted reference (jku/x5u)");
79
+ }
80
+ const kid = header.kid;
81
+ if (typeof kid !== "string" || !KID_PATTERN.test(kid) || kid.length > 256) {
82
+ throw new Error("Invalid kid");
83
+ }
84
+ let publicKeyPem;
85
+ if (options.keyResolver) {
86
+ publicKeyPem = await options.keyResolver(kid);
87
+ }
88
+ else if (options.jwksUri) {
89
+ publicKeyPem = (await getJwksClient(options.jwksUri, options.issuer).getSigningKey(kid)).getPublicKey();
90
+ }
91
+ else {
92
+ throw new Error("verifyServiceConfigTicket requires either jwksUri or keyResolver");
93
+ }
94
+ let verified;
95
+ try {
96
+ verified = jwt.verify(token, publicKeyPem, {
97
+ algorithms: [ALLOWED_ALGORITHM],
98
+ issuer: options.issuer,
99
+ audience: CONFIG_TICKET_AUDIENCE,
100
+ clockTolerance: options.clockToleranceSeconds ?? 0,
101
+ complete: false
102
+ });
103
+ }
104
+ catch (error) {
105
+ throw new Error(`Config ticket verification failed: ${error.message}`);
106
+ }
107
+ if (!verified || typeof verified !== "object") {
108
+ throw new Error("Config ticket verifier returned non-object claims");
109
+ }
110
+ const claims = ServiceConfigTicketClaimsSchema.parse(verified);
111
+ const now = Math.floor(Date.now() / 1000);
112
+ const tolerance = options.clockToleranceSeconds ?? 0;
113
+ if (claims.exp <= now - tolerance) {
114
+ throw new Error("Config ticket expired (manual re-check)");
115
+ }
116
+ if (claims.iss !== options.issuer) {
117
+ throw new Error(`Config ticket issuer mismatch (manual re-check) (${claims.iss} != ${options.issuer}`);
118
+ }
119
+ const auds = Array.isArray(claims.aud) ? claims.aud : [claims.aud];
120
+ if (!auds.includes(CONFIG_TICKET_AUDIENCE)) {
121
+ throw new Error(`Config ticket audience mismatch (manual re-check) (${CONFIG_TICKET_AUDIENCE} != ${auds.join(',')})`);
122
+ }
123
+ if (claims.serviceId !== options.serviceId) {
124
+ throw new Error(`Config ticket serviceId mismatch (manual re-check) (${claims.serviceId} != ${options.serviceId})`);
125
+ }
126
+ return claims;
127
+ }
128
+ /**
129
+ * Build a `validateTicket` callback for `registerServiceConfigRoutes` that
130
+ * verifies CP-signed tickets against the CP JWKS. Returns null (not throw) on
131
+ * any failure so the route responds 401 rather than 500.
132
+ */
133
+ export function createCpConfigTicketValidator(options) {
134
+ return async (ticketValue) => {
135
+ if (!ticketValue)
136
+ return null;
137
+ try {
138
+ return await verifyServiceConfigTicket(ticketValue, options);
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ };
144
+ }
145
+ export function clearConfigTicketJwksCache() {
146
+ clientCache.clear();
147
+ }
148
+ function parseHeader(encodedHeader) {
149
+ let json;
150
+ try {
151
+ json = Buffer.from(encodedHeader, "base64url").toString("utf8");
152
+ }
153
+ catch {
154
+ throw new Error("Config ticket header is not valid base64url");
155
+ }
156
+ let parsed;
157
+ try {
158
+ parsed = JSON.parse(json);
159
+ }
160
+ catch {
161
+ throw new Error("Config ticket header is not valid JSON");
162
+ }
163
+ if (!parsed || typeof parsed !== "object") {
164
+ throw new Error("Config ticket header is not an object");
165
+ }
166
+ return parsed;
167
+ }
168
+ //# sourceMappingURL=configTicket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configTicket.js","sourceRoot":"","sources":["../../src/runtime/configTicket.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { JsonValue } from "../contracts/json.js";
2
+ import { type BetterPortalObservability, type ObservabilityAttributes } from "../contracts/observability.js";
3
+ import { type HeaderMap } from "./http.js";
4
+ import { type NegotiatedViewResponse } from "./view.js";
5
+ export type BetterPortalEvent = import("h3").H3Event;
6
+ export type BetterPortalH3App = import("h3").H3;
7
+ type BetterPortalCorsOptions = import("h3").CorsOptions;
8
+ export interface BetterPortalAppOptions {
9
+ createRequestObservability?: (name: string, attributes: ObservabilityAttributes) => BetterPortalObservability;
10
+ }
11
+ export declare function eventObservability(event: BetterPortalEvent): BetterPortalObservability | undefined;
12
+ /**
13
+ * The direct socket peer IP of the request - i.e. NOT derived from
14
+ * X-Forwarded-For. Use this to decide whether a request actually arrived from a
15
+ * trusted upstream proxy before honouring any proxy-supplied headers.
16
+ */
17
+ export declare function getEventPeerIp(event: BetterPortalEvent): string | undefined;
18
+ export declare function createBetterPortalApp(options?: BetterPortalAppOptions): BetterPortalH3App;
19
+ export declare function createBetterPortalNodeHandler(app: BetterPortalH3App): import("h3").NodeHandler;
20
+ export declare function eventHeaders(event: BetterPortalEvent): HeaderMap;
21
+ export declare function acceptHeaderFromEvent(event: BetterPortalEvent): string | undefined;
22
+ export declare function jsonResponse(body: JsonValue, status?: number, headers?: HeadersInit): Response;
23
+ export declare function htmlResponse(body: string, status?: number, contentType?: string, headers?: HeadersInit): Response;
24
+ export declare function negotiatedResponseToWebResponse(negotiated: NegotiatedViewResponse): Response;
25
+ export declare function handleCorsRequest(event: BetterPortalEvent, options: BetterPortalCorsOptions): Response | false;
26
+ export declare function withObservedEvent<T>(event: BetterPortalEvent, observability: BetterPortalObservability, name: string, handler: (event: BetterPortalEvent, span: BetterPortalObservability) => Promise<T> | T, attributes?: ObservabilityAttributes): Promise<T>;
27
+ export {};
28
+ //# sourceMappingURL=h3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"h3.d.ts","sourceRoot":"","sources":["../../src/runtime/h3.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC7B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAExD,MAAM,MAAM,iBAAiB,GAAG,OAAO,IAAI,EAAE,OAAO,CAAC;AACrD,MAAM,MAAM,iBAAiB,GAAG,OAAO,IAAI,EAAE,EAAE,CAAC;AAChD,KAAK,uBAAuB,GAAG,OAAO,IAAI,EAAE,WAAW,CAAC;AAMxD,MAAM,WAAW,sBAAsB;IACrC,0BAA0B,CAAC,EAAE,CAC3B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,uBAAuB,KAChC,yBAAyB,CAAC;CAChC;AAuBD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,GAAG,yBAAyB,GAAG,SAAS,CAElG;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS,CAE3E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,sBAA2B,GAAG,iBAAiB,CAwD7F;AAED,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,iBAAiB,4BAEnE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,SAAS,CAEhE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS,CAElF;AAQD,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,SAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,QAAQ,CAQ3F;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAM,EAAE,WAAW,SAAc,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,QAAQ,CAQnH;AAED,wBAAgB,+BAA+B,CAAC,UAAU,EAAE,sBAAsB,GAAG,QAAQ,CAQ5F;AAUD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,uBAAuB,GAAG,QAAQ,GAAG,KAAK,CAO9G;AAgDD,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,yBAAyB,EACxC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,IAAI,EAAE,yBAAyB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EACtF,UAAU,GAAE,uBAA4B,GACvC,OAAO,CAAC,CAAC,CAAC,CAsBZ"}
@@ -0,0 +1,199 @@
1
+ import { H3, getRequestIP, getRequestURL, handleCors, toNodeHandler } from "h3";
2
+ import { toHtmlString } from "./http.js";
3
+ function observedEventState(event) {
4
+ return event.__bpObservedEvent;
5
+ }
6
+ function byteCountFromHeader(value) {
7
+ if (!value)
8
+ return 0;
9
+ const parsed = Number(value);
10
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
11
+ }
12
+ function responseByteCount(response) {
13
+ const contentLength = byteCountFromHeader(response.headers.get("content-length"));
14
+ if (contentLength > 0)
15
+ return contentLength;
16
+ return 0;
17
+ }
18
+ export function eventObservability(event) {
19
+ return observedEventState(event)?.observability;
20
+ }
21
+ /**
22
+ * The direct socket peer IP of the request - i.e. NOT derived from
23
+ * X-Forwarded-For. Use this to decide whether a request actually arrived from a
24
+ * trusted upstream proxy before honouring any proxy-supplied headers.
25
+ */
26
+ export function getEventPeerIp(event) {
27
+ return getRequestIP(event, { xForwardedFor: false }) ?? undefined;
28
+ }
29
+ export function createBetterPortalApp(options = {}) {
30
+ const app = new H3({
31
+ onRequest: (event) => {
32
+ const obs = options.createRequestObservability?.("bp.http.request", requestAttributes(event));
33
+ if (obs) {
34
+ event.__bpObservedEvent = {
35
+ observability: obs,
36
+ startedAt: performance.now()
37
+ };
38
+ }
39
+ },
40
+ onResponse: (response, event) => {
41
+ event.res.headers.forEach((value, name) => {
42
+ if (!response.headers.has(name))
43
+ response.headers.set(name, value);
44
+ });
45
+ const state = observedEventState(event);
46
+ if (!state)
47
+ return;
48
+ const durationMs = roundedDuration(performance.now() - state.startedAt);
49
+ const requestUrl = getRequestURL(event);
50
+ const requestIp = getRequestIP(event, { xForwardedFor: true }) ?? "";
51
+ const bpContext = event;
52
+ const attrs = {
53
+ method: event.req.method,
54
+ path: requestUrl.pathname,
55
+ status: response.status,
56
+ durationMs,
57
+ callerIp: requestIp,
58
+ host: event.req.headers.get("host") ?? "",
59
+ referer: event.req.headers.get("referer") ?? "",
60
+ tenantId: bpContext.__bpTenantId ?? "",
61
+ appId: bpContext.__bpAppId ?? "",
62
+ requestBytes: byteCountFromHeader(event.req.headers.get("content-length")),
63
+ responseBytes: responseByteCount(response)
64
+ };
65
+ const message = "BP REQUEST: {method} {path} -> {status} in {durationMs}ms callerIp={callerIp} host={host} referer={referer} tenant={tenantId} app={appId} requestBytes={requestBytes} responseBytes={responseBytes}";
66
+ if (response.status >= 500) {
67
+ state.observability.logger.error(message, attrs);
68
+ }
69
+ else if (response.status >= 400) {
70
+ state.observability.logger.warn(message, attrs);
71
+ }
72
+ else {
73
+ state.observability.logger.info(message, attrs);
74
+ }
75
+ state.observability.end({
76
+ "http.response.status_code": response.status,
77
+ "duration.ms": durationMs
78
+ });
79
+ }
80
+ });
81
+ return observeRegisteredHandlers(app);
82
+ }
83
+ export function createBetterPortalNodeHandler(app) {
84
+ return toNodeHandler(app);
85
+ }
86
+ export function eventHeaders(event) {
87
+ return event.req.headers;
88
+ }
89
+ export function acceptHeaderFromEvent(event) {
90
+ return event.req.headers.get("accept") ?? undefined;
91
+ }
92
+ function contentTypeWithCharset(contentType) {
93
+ return contentType.toLowerCase().includes("charset=")
94
+ ? contentType
95
+ : `${contentType}; charset=utf-8`;
96
+ }
97
+ export function jsonResponse(body, status = 200, headers) {
98
+ return new Response(JSON.stringify(body), {
99
+ status,
100
+ headers: {
101
+ "content-type": "application/json; charset=utf-8",
102
+ ...headers
103
+ }
104
+ });
105
+ }
106
+ export function htmlResponse(body, status = 200, contentType = "text/html", headers) {
107
+ return new Response(body, {
108
+ status,
109
+ headers: {
110
+ "content-type": contentTypeWithCharset(contentType),
111
+ ...headers
112
+ }
113
+ });
114
+ }
115
+ export function negotiatedResponseToWebResponse(negotiated) {
116
+ if (negotiated.contentType.startsWith("text/html")) {
117
+ return htmlResponse(toHtmlString(negotiated.body ?? ""), negotiated.status, negotiated.contentType);
118
+ }
119
+ return jsonResponse(negotiated.body, negotiated.status, {
120
+ "content-type": contentTypeWithCharset(negotiated.contentType)
121
+ });
122
+ }
123
+ function normalizeHttpResponse(response) {
124
+ return new Response(response.body ?? null, {
125
+ status: response.status,
126
+ statusText: response.statusText,
127
+ headers: response.headers
128
+ });
129
+ }
130
+ export function handleCorsRequest(event, options) {
131
+ const corsResponse = handleCors(event, options);
132
+ if (corsResponse === false) {
133
+ return false;
134
+ }
135
+ return corsResponse instanceof Response ? corsResponse : normalizeHttpResponse(corsResponse);
136
+ }
137
+ function statusCodeFromResult(event, result) {
138
+ if (result instanceof Response) {
139
+ return result.status;
140
+ }
141
+ return event.res.status || 200;
142
+ }
143
+ function requestAttributes(event) {
144
+ const requestUrl = getRequestURL(event);
145
+ const requestIp = getRequestIP(event, { xForwardedFor: true });
146
+ return {
147
+ "http.request.method": event.req.method,
148
+ "url.full": requestUrl.toString(),
149
+ "url.path": requestUrl.pathname,
150
+ "server.address": event.req.headers.get("host") ?? "",
151
+ "http.request.header.referer": event.req.headers.get("referer") ?? "",
152
+ "network.protocol.name": requestUrl.protocol.replace(":", ""),
153
+ ...(requestIp ? { "client.address": requestIp } : {})
154
+ };
155
+ }
156
+ function roundedDuration(durationMs) {
157
+ return Math.round(durationMs * 100) / 100;
158
+ }
159
+ function observeRegisteredHandlers(app) {
160
+ const registrars = app;
161
+ const names = ["get", "post", "put", "patch", "delete", "options", "use"];
162
+ for (const name of names) {
163
+ const original = registrars[name].bind(app);
164
+ registrars[name] = (path, handler) => original(path, async (event) => {
165
+ const obs = eventObservability(event);
166
+ if (!obs)
167
+ return handler(event);
168
+ return withObservedEvent(event, obs, "bp.h3.handler", handler, {
169
+ "http.route": path,
170
+ "http.route.method": name.toUpperCase()
171
+ });
172
+ });
173
+ }
174
+ return app;
175
+ }
176
+ export async function withObservedEvent(event, observability, name, handler, attributes = {}) {
177
+ const span = observability.startSpan(name, {
178
+ ...requestAttributes(event),
179
+ ...attributes
180
+ });
181
+ try {
182
+ const result = await handler(event, span);
183
+ span.end({
184
+ "http.response.status_code": statusCodeFromResult(event, result)
185
+ });
186
+ return result;
187
+ }
188
+ catch (error) {
189
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
190
+ span.error(normalizedError, {
191
+ "error.name": normalizedError.name
192
+ });
193
+ span.end({
194
+ "http.response.status_code": event.res.status || 500
195
+ });
196
+ throw error;
197
+ }
198
+ }
199
+ //# sourceMappingURL=h3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"h3.js","sourceRoot":"","sources":["../../src/runtime/h3.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAMhF,OAAO,EAAE,YAAY,EAAkB,MAAM,WAAW,CAAC;AAuBzD,SAAS,kBAAkB,CAAC,KAAwB;IAClD,OAAQ,KAA+D,CAAC,iBAAiB,CAAC;AAC5F,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAoB;IAC/C,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC;IACrB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAkB;IAC3C,MAAM,aAAa,GAAG,mBAAmB,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAClF,IAAI,aAAa,GAAG,CAAC;QAAE,OAAO,aAAa,CAAC;IAC5C,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAwB;IACzD,OAAO,kBAAkB,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;AAClD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAAwB;IACrD,OAAO,YAAY,CAAC,KAAK,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,IAAI,SAAS,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,UAAkC,EAAE;IACxE,MAAM,GAAG,GAAG,IAAI,EAAE,CAAC;QACjB,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YACnB,MAAM,GAAG,GAAG,OAAO,CAAC,0BAA0B,EAAE,CAC9C,iBAAiB,EACjB,iBAAiB,CAAC,KAAK,CAAC,CACzB,CAAC;YACF,IAAI,GAAG,EAAE,CAAC;gBACP,KAA+D,CAAC,iBAAiB,GAAG;oBACnF,aAAa,EAAE,GAAG;oBAClB,SAAS,EAAE,WAAW,CAAC,GAAG,EAAE;iBAC7B,CAAC;YACJ,CAAC;QACH,CAAC;QACD,UAAU,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE;YAC9B,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;gBACxC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACrE,CAAC,CAAC,CAAC;YAEH,MAAM,KAAK,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;YACxC,IAAI,CAAC,KAAK;gBAAE,OAAO;YAEnB,MAAM,UAAU,GAAG,eAAe,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;YACxE,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;YACxC,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;YACrE,MAAM,SAAS,GAAI,KAAkE,CAAC;YACtF,MAAM,KAAK,GAAG;gBACZ,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM;gBACxB,IAAI,EAAE,UAAU,CAAC,QAAQ;gBACzB,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,UAAU;gBACV,QAAQ,EAAE,SAAS;gBACnB,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE;gBACzC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE;gBAC/C,QAAQ,EAAE,SAAS,CAAC,YAAY,IAAI,EAAE;gBACtC,KAAK,EAAE,SAAS,CAAC,SAAS,IAAI,EAAE;gBAChC,YAAY,EAAE,mBAAmB,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;gBAC1E,aAAa,EAAE,iBAAiB,CAAC,QAAQ,CAAC;aAC3C,CAAC;YACF,MAAM,OAAO,GAAG,qMAAqM,CAAC;YAEtN,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;gBAC3B,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACnD,CAAC;iBAAM,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;gBAClC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAClD,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAClD,CAAC;YAED,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC;gBACtB,2BAA2B,EAAE,QAAQ,CAAC,MAAM;gBAC5C,aAAa,EAAE,UAAU;aAC1B,CAAC,CAAC;QACL,CAAC;KACF,CAAC,CAAC;IACH,OAAO,yBAAyB,CAAC,GAAG,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,GAAsB;IAClE,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,OAAO,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAwB;IAC5D,OAAO,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC;AACtD,CAAC;AAED,SAAS,sBAAsB,CAAC,WAAmB;IACjD,OAAO,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC;QACnD,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,GAAG,WAAW,iBAAiB,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAe,EAAE,MAAM,GAAG,GAAG,EAAE,OAAqB;IAC/E,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE;YACP,cAAc,EAAE,iCAAiC;YACjD,GAAG,OAAO;SACX;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,MAAM,GAAG,GAAG,EAAE,WAAW,GAAG,WAAW,EAAE,OAAqB;IACvG,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM;QACN,OAAO,EAAE;YACP,cAAc,EAAE,sBAAsB,CAAC,WAAW,CAAC;YACnD,GAAG,OAAO;SACX;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,UAAkC;IAChF,IAAI,UAAU,CAAC,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QACnD,OAAO,YAAY,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IACtG,CAAC;IAED,OAAO,YAAY,CAAC,UAAU,CAAC,IAAiB,EAAE,UAAU,CAAC,MAAM,EAAE;QACnE,cAAc,EAAE,sBAAsB,CAAC,UAAU,CAAC,WAAW,CAAC;KAC/D,CAAC,CAAC;AACL,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAwB;IACrD,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,IAAI,EAAE;QACzC,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,OAAO,EAAE,QAAQ,CAAC,OAAO;KAC1B,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAwB,EAAE,OAAgC;IAC1F,MAAM,YAAY,GAAG,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAChD,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,YAAY,YAAY,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC;AAC/F,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAwB,EAAE,MAAe;IACrE,IAAI,MAAM,YAAY,QAAQ,EAAE,CAAC;QAC/B,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;IAED,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;AACjC,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAwB;IACjD,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/D,OAAO;QACL,qBAAqB,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM;QACvC,UAAU,EAAE,UAAU,CAAC,QAAQ,EAAE;QACjC,UAAU,EAAE,UAAU,CAAC,QAAQ;QAC/B,gBAAgB,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE;QACrD,6BAA6B,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE;QACrE,uBAAuB,EAAE,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;QAC7D,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,UAAkB;IACzC,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AAC5C,CAAC;AAED,SAAS,yBAAyB,CAAC,GAAsB;IACvD,MAAM,UAAU,GAAG,GAAoF,CAAC;IACxG,MAAM,KAAK,GAAqC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAE5G,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5C,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACnE,MAAM,GAAG,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;YACtC,IAAI,CAAC,GAAG;gBAAE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;YAChC,OAAO,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE;gBAC7D,YAAY,EAAE,IAAI;gBAClB,mBAAmB,EAAE,IAAI,CAAC,WAAW,EAAE;aACxC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAwB,EACxB,aAAwC,EACxC,IAAY,EACZ,OAAsF,EACtF,aAAsC,EAAE;IAExC,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,CAAC,IAAI,EAAE;QACzC,GAAG,iBAAiB,CAAC,KAAK,CAAC;QAC3B,GAAG,UAAU;KACd,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG,CAAC;YACP,2BAA2B,EAAE,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC;SACjE,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,eAAe,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAClF,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE;YAC1B,YAAY,EAAE,eAAe,CAAC,IAAI;SACnC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC;YACP,2BAA2B,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG;SACrD,CAAC,CAAC;QACH,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { BaseSchema, Infer } from "anyvali";
2
+ import type { RawRouteHandler, RouteHandlerContext, RouteHandler } from "../contracts/route.js";
3
+ /**
4
+ * Schema configuration for a route handler.
5
+ * `response` is required; `query`, `headers`, and `request` are optional.
6
+ */
7
+ export interface HandlerSchemas<TResponse extends BaseSchema<unknown, unknown>, TQuery extends BaseSchema<unknown, unknown> | undefined = undefined, THeaders extends BaseSchema<unknown, unknown> | undefined = undefined, TRequest extends BaseSchema<unknown, unknown> | undefined = undefined> {
8
+ readonly response: TResponse;
9
+ readonly query?: TQuery;
10
+ readonly headers?: THeaders;
11
+ readonly request?: TRequest;
12
+ }
13
+ /** Resolve a schema to its inferred output type, or fall back to a default. */
14
+ type SchemaOutput<T extends BaseSchema<unknown, unknown> | undefined, TDefault> = T extends BaseSchema<unknown, unknown> ? Infer<T> : TDefault;
15
+ /** Fully-typed handler context derived from schema configuration. */
16
+ type TypedHandlerContext<TSchemas extends HandlerSchemas<BaseSchema<unknown, unknown>, any, any, any>, TParams = Record<string, string>, TPlugin = unknown, TServiceConfig = Record<string, unknown>> = RouteHandlerContext<TParams, SchemaOutput<TSchemas["query"], Record<string, unknown>>, SchemaOutput<TSchemas["headers"], Record<string, string>>, SchemaOutput<TSchemas["request"], Record<string, unknown>>, TPlugin, TServiceConfig>;
17
+ /**
18
+ * Create a type-safe route handler with framework-enforced validation.
19
+ *
20
+ * - TypeScript enforces the return type matches `Infer<ResponseSchema>` at compile time.
21
+ * - At runtime, the response is validated through `schemas.response.parse()` as a safety net.
22
+ * - The handler's `ctx` is fully typed from the provided schemas - no manual `.parse()` needed.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * export const handleGet = createHandler(
27
+ * { response: ResponseSchema, query: QuerySchema },
28
+ * (ctx) => ({
29
+ * greeting: `Hello, ${ctx.query.name}`,
30
+ * themeHint: "bootstrap1",
31
+ * supports: ["application/json", "text/html"]
32
+ * })
33
+ * );
34
+ * ```
35
+ */
36
+ export declare function createHandler<TResponse extends BaseSchema<unknown, unknown>, TQuery extends BaseSchema<unknown, unknown> | undefined = undefined, THeaders extends BaseSchema<unknown, unknown> | undefined = undefined, TRequest extends BaseSchema<unknown, unknown> | undefined = undefined, TParams = Record<string, string>, TPlugin = unknown, TServiceConfig = Record<string, unknown>>(schemas: HandlerSchemas<TResponse, TQuery, THeaders, TRequest>, handler: (ctx: TypedHandlerContext<HandlerSchemas<TResponse, TQuery, THeaders, TRequest>, TParams, TPlugin, TServiceConfig>) => Infer<TResponse> | Promise<Infer<TResponse>>): RouteHandler<TParams, SchemaOutput<TQuery, Record<string, unknown>>, SchemaOutput<THeaders, Record<string, string>>, SchemaOutput<TRequest, Record<string, unknown>>, Infer<TResponse>, TPlugin, TServiceConfig>;
37
+ export declare namespace createHandler {
38
+ function forContext<TPlugin = unknown, TServiceConfig = Record<string, unknown>>(): <TResponse extends BaseSchema<unknown, unknown>, TQuery extends BaseSchema<unknown, unknown> | undefined = undefined, THeaders extends BaseSchema<unknown, unknown> | undefined = undefined, TRequest extends BaseSchema<unknown, unknown> | undefined = undefined, TParams = Record<string, string>>(schemas: HandlerSchemas<TResponse, TQuery, THeaders, TRequest>, handler: (ctx: TypedHandlerContext<HandlerSchemas<TResponse, TQuery, THeaders, TRequest>, TParams, TPlugin, TServiceConfig>) => Infer<TResponse> | Promise<Infer<TResponse>>) => RouteHandler<TParams, SchemaOutput<TQuery, Record<string, unknown>>, SchemaOutput<THeaders, Record<string, string>>, SchemaOutput<TRequest, Record<string, unknown>>, Infer<TResponse>, TPlugin, TServiceConfig>;
39
+ }
40
+ export type RawHandlerSchemas<TQuery extends BaseSchema<unknown, unknown> | undefined = undefined, THeaders extends BaseSchema<unknown, unknown> | undefined = undefined, TRequest extends BaseSchema<unknown, unknown> | undefined = undefined, TMultipart extends BaseSchema<unknown, unknown> | undefined = undefined> = {
41
+ readonly query?: TQuery;
42
+ readonly headers?: THeaders;
43
+ readonly request?: TRequest;
44
+ readonly multipart?: TMultipart;
45
+ };
46
+ export declare function createRawHandler<TQuery extends BaseSchema<unknown, unknown> | undefined = undefined, THeaders extends BaseSchema<unknown, unknown> | undefined = undefined, TRequest extends BaseSchema<unknown, unknown> | undefined = undefined, TMultipart extends BaseSchema<unknown, unknown> | undefined = undefined, TParams = Record<string, string>, TPlugin = unknown, TServiceConfig = Record<string, unknown>>(_schemas: RawHandlerSchemas<TQuery, THeaders, TRequest, TMultipart>, handler: (ctx: RouteHandlerContext<TParams, SchemaOutput<TQuery, Record<string, unknown>>, SchemaOutput<THeaders, Record<string, string>>, SchemaOutput<TRequest, Record<string, unknown>>, TPlugin, TServiceConfig> & {
47
+ readonly multipart: SchemaOutput<TMultipart, never>;
48
+ }) => Response | Promise<Response>): RawRouteHandler<TParams, SchemaOutput<TQuery, Record<string, unknown>>, SchemaOutput<THeaders, Record<string, string>>, SchemaOutput<TRequest, Record<string, unknown>>, TPlugin, TServiceConfig>;
49
+ export declare namespace createRawHandler {
50
+ function forContext<TPlugin = unknown, TServiceConfig = Record<string, unknown>>(): <TQuery extends BaseSchema<unknown, unknown> | undefined = undefined, THeaders extends BaseSchema<unknown, unknown> | undefined = undefined, TRequest extends BaseSchema<unknown, unknown> | undefined = undefined, TMultipart extends BaseSchema<unknown, unknown> | undefined = undefined, TParams = Record<string, string>>(schemas: RawHandlerSchemas<TQuery, THeaders, TRequest, TMultipart>, handler: (ctx: RouteHandlerContext<TParams, SchemaOutput<TQuery, Record<string, unknown>>, SchemaOutput<THeaders, Record<string, string>>, SchemaOutput<TRequest, Record<string, unknown>>, TPlugin, TServiceConfig> & {
51
+ readonly multipart: SchemaOutput<TMultipart, never>;
52
+ }) => Response | Promise<Response>) => RawRouteHandler<TParams, SchemaOutput<TQuery, Record<string, unknown>>, SchemaOutput<THeaders, Record<string, string>>, SchemaOutput<TRequest, Record<string, unknown>>, TPlugin, TServiceConfig>;
53
+ }
54
+ export {};
55
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/runtime/handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ // -- Factory ----------------------------------------------------------
2
+ /**
3
+ * Create a type-safe route handler with framework-enforced validation.
4
+ *
5
+ * - TypeScript enforces the return type matches `Infer<ResponseSchema>` at compile time.
6
+ * - At runtime, the response is validated through `schemas.response.parse()` as a safety net.
7
+ * - The handler's `ctx` is fully typed from the provided schemas - no manual `.parse()` needed.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * export const handleGet = createHandler(
12
+ * { response: ResponseSchema, query: QuerySchema },
13
+ * (ctx) => ({
14
+ * greeting: `Hello, ${ctx.query.name}`,
15
+ * themeHint: "bootstrap1",
16
+ * supports: ["application/json", "text/html"]
17
+ * })
18
+ * );
19
+ * ```
20
+ */
21
+ export function createHandler(schemas, handler) {
22
+ return async (ctx) => {
23
+ const result = await handler(ctx);
24
+ // Runtime validation - catches any drift between TypeScript types and actual data.
25
+ return schemas.response.parse(result);
26
+ };
27
+ }
28
+ (function (createHandler) {
29
+ function forContext() {
30
+ return createHandler;
31
+ }
32
+ createHandler.forContext = forContext;
33
+ })(createHandler || (createHandler = {}));
34
+ export function createRawHandler(_schemas, handler) {
35
+ const raw = (async (ctx) => {
36
+ const result = await handler(ctx);
37
+ if (!(result instanceof Response)) {
38
+ throw new TypeError("createRawHandler handlers must return a Response");
39
+ }
40
+ return result;
41
+ });
42
+ Object.defineProperty(raw, "__bpRawHandler", { value: true });
43
+ return raw;
44
+ }
45
+ (function (createRawHandler) {
46
+ function forContext() {
47
+ return createRawHandler;
48
+ }
49
+ createRawHandler.forContext = forContext;
50
+ })(createRawHandler || (createRawHandler = {}));
51
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/runtime/handler.ts"],"names":[],"mappings":"AA4CA,wEAAwE;AAExE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,aAAa,CAS3B,OAA8D,EAC9D,OAEiD;IAUjD,OAAO,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,GAAmH,CACpH,CAAC;QACF,mFAAmF;QACnF,OAAO,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAqB,CAAC;IAC5D,CAAC,CAAC;AACJ,CAAC;AAED,WAAiB,aAAa;IAC5B,SAAgB,UAAU;QACxB,OAAO,aAmBN,CAAC;IACJ,CAAC;IArBe,wBAAU,aAqBzB,CAAA;AACH,CAAC,EAvBgB,aAAa,KAAb,aAAa,QAuB7B;AAcD,MAAM,UAAU,gBAAgB,CAS9B,QAAmE,EACnE,OASiC;IASjC,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,GAA4D,EAAE,EAAE;QAClF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAoC,CAAC,CAAC;QACnE,IAAI,CAAC,CAAC,MAAM,YAAY,QAAQ,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,SAAS,CAAC,kDAAkD,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAOA,CAAC;IACF,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,OAAO,GAAG,CAAC;AACb,CAAC;AAED,WAAiB,gBAAgB;IAC/B,SAAgB,UAAU;QACxB,OAAO,gBAyBN,CAAC;IACJ,CAAC;IA3Be,2BAAU,aA2BzB,CAAA;AACH,CAAC,EA7BgB,gBAAgB,KAAhB,gBAAgB,QA6BhC"}
@@ -0,0 +1,13 @@
1
+ import { type HtmlRenderable } from "./view.js";
2
+ export type HeaderMap = Headers | Record<string, string | string[] | undefined>;
3
+ export interface BetterPortalHeaderTrustOptions {
4
+ trustedProxyHeaders?: boolean;
5
+ cfProxy?: boolean;
6
+ }
7
+ export declare function hostFromHeaderValue(value?: string): string | null;
8
+ export declare function resolveEmbeddedSourceHeader(headers: HeaderMap, options?: BetterPortalHeaderTrustOptions): string | undefined;
9
+ export declare function resolveThemeSourceHeader(headers: HeaderMap, options?: BetterPortalHeaderTrustOptions): string | undefined;
10
+ export declare function resolveEmbeddedHostname(headers: HeaderMap, options?: BetterPortalHeaderTrustOptions): string | null;
11
+ export declare function resolveThemeHostname(headers: HeaderMap, options?: BetterPortalHeaderTrustOptions): string | null;
12
+ export declare function toHtmlString(body: HtmlRenderable): string;
13
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/runtime/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AAEhF,MAAM,WAAW,8BAA8B;IAC7C,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA2CD,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA2BjE;AAqBD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,GAAE,8BAAmC,GAAG,MAAM,GAAG,SAAS,CAYhI;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,GAAE,8BAAmC,GAAG,MAAM,GAAG,SAAS,CAU7H;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,GAAE,8BAAmC,GAAG,MAAM,GAAG,IAAI,CAEvH;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,GAAE,8BAAmC,GAAG,MAAM,GAAG,IAAI,CAEpH;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAEzD"}