@samebase/convex-hosting 0.3.2 → 0.4.0

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 (193) hide show
  1. package/CHANGELOG.md +10 -134
  2. package/README.md +136 -398
  3. package/dist/cli/convex.d.ts +63 -0
  4. package/dist/cli/convex.js +108 -0
  5. package/dist/cli/convex.js.map +1 -0
  6. package/dist/cli/files.d.ts +18 -0
  7. package/dist/cli/files.js +106 -0
  8. package/dist/cli/files.js.map +1 -0
  9. package/dist/cli/index.d.ts +1 -11
  10. package/dist/cli/index.js +3 -58
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/main.d.ts +5 -0
  13. package/dist/cli/main.js +346 -0
  14. package/dist/cli/main.js.map +1 -0
  15. package/dist/client/index.d.ts +8 -115
  16. package/dist/client/index.js +65 -242
  17. package/dist/client/index.js.map +1 -1
  18. package/dist/client/types.d.ts +5 -0
  19. package/dist/client/version-module.d.ts +6 -0
  20. package/dist/component/_generated/api.d.ts +0 -5
  21. package/dist/component/_generated/api.js.map +1 -1
  22. package/dist/component/_generated/component.d.ts +20 -18
  23. package/dist/component/_generated/dataModel.d.ts +0 -1
  24. package/dist/component/_generated/server.d.ts +12 -1
  25. package/dist/component/_generated/server.js +2 -0
  26. package/dist/component/_generated/server.js.map +1 -1
  27. package/dist/component/convex.config.d.ts +0 -1
  28. package/dist/component/lib.d.ts +175 -156
  29. package/dist/component/lib.js +396 -710
  30. package/dist/component/lib.js.map +1 -1
  31. package/dist/component/schema.d.ts +76 -72
  32. package/dist/component/schema.js +35 -59
  33. package/dist/component/schema.js.map +1 -1
  34. package/dist/workers-shared/asset-worker/src/analytics.d.ts +39 -0
  35. package/dist/workers-shared/asset-worker/src/analytics.js +86 -0
  36. package/dist/workers-shared/asset-worker/src/analytics.js.map +1 -0
  37. package/dist/workers-shared/asset-worker/src/compatibility-flags.d.ts +19 -0
  38. package/dist/workers-shared/asset-worker/src/compatibility-flags.js +29 -0
  39. package/dist/workers-shared/asset-worker/src/compatibility-flags.js.map +1 -0
  40. package/dist/workers-shared/asset-worker/src/configuration.d.ts +3 -0
  41. package/dist/workers-shared/asset-worker/src/configuration.js +30 -0
  42. package/dist/workers-shared/asset-worker/src/configuration.js.map +1 -0
  43. package/dist/workers-shared/asset-worker/src/constants.d.ts +1 -0
  44. package/dist/workers-shared/asset-worker/src/constants.js +3 -0
  45. package/dist/workers-shared/asset-worker/src/constants.js.map +1 -0
  46. package/dist/workers-shared/asset-worker/src/handler.d.ts +28 -0
  47. package/dist/workers-shared/asset-worker/src/handler.js +717 -0
  48. package/dist/workers-shared/asset-worker/src/handler.js.map +1 -0
  49. package/dist/workers-shared/asset-worker/src/types.d.ts +13 -0
  50. package/dist/workers-shared/asset-worker/src/types.js +2 -0
  51. package/dist/workers-shared/asset-worker/src/types.js.map +1 -0
  52. package/dist/workers-shared/asset-worker/src/utils/base-path.d.ts +29 -0
  53. package/dist/workers-shared/asset-worker/src/utils/base-path.js +56 -0
  54. package/dist/workers-shared/asset-worker/src/utils/base-path.js.map +1 -0
  55. package/dist/workers-shared/asset-worker/src/utils/headers.d.ts +11 -0
  56. package/dist/workers-shared/asset-worker/src/utils/headers.js +77 -0
  57. package/dist/workers-shared/asset-worker/src/utils/headers.js.map +1 -0
  58. package/dist/workers-shared/asset-worker/src/utils/rules-engine.d.ts +23 -0
  59. package/dist/workers-shared/asset-worker/src/utils/rules-engine.js +133 -0
  60. package/dist/workers-shared/asset-worker/src/utils/rules-engine.js.map +1 -0
  61. package/dist/workers-shared/asset-worker/src/worker.d.ts +14 -0
  62. package/dist/workers-shared/asset-worker/src/worker.js +12 -0
  63. package/dist/workers-shared/asset-worker/src/worker.js.map +1 -0
  64. package/dist/workers-shared/utils/base-path.d.ts +32 -0
  65. package/dist/workers-shared/utils/base-path.js +94 -0
  66. package/dist/workers-shared/utils/base-path.js.map +1 -0
  67. package/dist/workers-shared/utils/configuration/constants.d.ts +15 -0
  68. package/dist/workers-shared/utils/configuration/constants.js +16 -0
  69. package/dist/workers-shared/utils/configuration/constants.js.map +1 -0
  70. package/dist/workers-shared/utils/configuration/constructConfiguration.d.ts +12 -0
  71. package/dist/workers-shared/utils/configuration/constructConfiguration.js +103 -0
  72. package/dist/workers-shared/utils/configuration/constructConfiguration.js.map +1 -0
  73. package/dist/workers-shared/utils/configuration/parseHeaders.d.ts +5 -0
  74. package/dist/workers-shared/utils/configuration/parseHeaders.js +181 -0
  75. package/dist/workers-shared/utils/configuration/parseHeaders.js.map +1 -0
  76. package/dist/workers-shared/utils/configuration/parseRedirects.d.ts +8 -0
  77. package/dist/workers-shared/utils/configuration/parseRedirects.js +129 -0
  78. package/dist/workers-shared/utils/configuration/parseRedirects.js.map +1 -0
  79. package/dist/workers-shared/utils/configuration/parseStaticRouting.d.ts +2 -0
  80. package/dist/workers-shared/utils/configuration/parseStaticRouting.js +94 -0
  81. package/dist/workers-shared/utils/configuration/parseStaticRouting.js.map +1 -0
  82. package/dist/workers-shared/utils/configuration/types.d.ts +38 -0
  83. package/dist/workers-shared/utils/configuration/types.js +2 -0
  84. package/dist/workers-shared/utils/configuration/types.js.map +1 -0
  85. package/dist/workers-shared/utils/configuration/validateURL.d.ts +3 -0
  86. package/dist/workers-shared/utils/configuration/validateURL.js +56 -0
  87. package/dist/workers-shared/utils/configuration/validateURL.js.map +1 -0
  88. package/dist/workers-shared/utils/constants.d.ts +28 -0
  89. package/dist/workers-shared/utils/constants.js +34 -0
  90. package/dist/workers-shared/utils/constants.js.map +1 -0
  91. package/dist/workers-shared/utils/helpers.d.ts +22 -0
  92. package/dist/workers-shared/utils/helpers.js +73 -0
  93. package/dist/workers-shared/utils/helpers.js.map +1 -0
  94. package/dist/workers-shared/utils/responses.d.ts +43 -0
  95. package/dist/workers-shared/utils/responses.js +125 -0
  96. package/dist/workers-shared/utils/responses.js.map +1 -0
  97. package/dist/workers-shared/utils/tracing.d.ts +3 -0
  98. package/dist/workers-shared/utils/tracing.js +29 -0
  99. package/dist/workers-shared/utils/tracing.js.map +1 -0
  100. package/dist/workers-shared/utils/types.d.ts +131 -0
  101. package/dist/workers-shared/utils/types.js +72 -0
  102. package/dist/workers-shared/utils/types.js.map +1 -0
  103. package/package.json +16 -32
  104. package/INTEGRATION.md +0 -399
  105. package/dist/cli/args.d.ts +0 -28
  106. package/dist/cli/args.d.ts.map +0 -1
  107. package/dist/cli/args.js +0 -148
  108. package/dist/cli/args.js.map +0 -1
  109. package/dist/cli/argumentChunks.d.ts +0 -3
  110. package/dist/cli/argumentChunks.d.ts.map +0 -1
  111. package/dist/cli/argumentChunks.js +0 -29
  112. package/dist/cli/argumentChunks.js.map +0 -1
  113. package/dist/cli/collectFiles.d.ts +0 -10
  114. package/dist/cli/collectFiles.d.ts.map +0 -1
  115. package/dist/cli/collectFiles.js +0 -53
  116. package/dist/cli/collectFiles.js.map +0 -1
  117. package/dist/cli/commands.d.ts +0 -8
  118. package/dist/cli/commands.d.ts.map +0 -1
  119. package/dist/cli/commands.js +0 -56
  120. package/dist/cli/commands.js.map +0 -1
  121. package/dist/cli/componentName.d.ts +0 -32
  122. package/dist/cli/componentName.d.ts.map +0 -1
  123. package/dist/cli/componentName.js +0 -43
  124. package/dist/cli/componentName.js.map +0 -1
  125. package/dist/cli/deploy.d.ts +0 -16
  126. package/dist/cli/deploy.d.ts.map +0 -1
  127. package/dist/cli/deploy.js +0 -283
  128. package/dist/cli/deploy.js.map +0 -1
  129. package/dist/cli/deployEnvironment.d.ts +0 -10
  130. package/dist/cli/deployEnvironment.d.ts.map +0 -1
  131. package/dist/cli/deployEnvironment.js +0 -12
  132. package/dist/cli/deployEnvironment.js.map +0 -1
  133. package/dist/cli/index.d.ts.map +0 -1
  134. package/dist/cli/upload.d.ts +0 -22
  135. package/dist/cli/upload.d.ts.map +0 -1
  136. package/dist/cli/upload.js +0 -630
  137. package/dist/cli/upload.js.map +0 -1
  138. package/dist/client/_generated/_ignore.d.ts +0 -1
  139. package/dist/client/_generated/_ignore.d.ts.map +0 -1
  140. package/dist/client/_generated/_ignore.js +0 -3
  141. package/dist/client/_generated/_ignore.js.map +0 -1
  142. package/dist/client/index.d.ts.map +0 -1
  143. package/dist/component/_generated/api.d.ts.map +0 -1
  144. package/dist/component/_generated/component.d.ts.map +0 -1
  145. package/dist/component/_generated/dataModel.d.ts.map +0 -1
  146. package/dist/component/_generated/server.d.ts.map +0 -1
  147. package/dist/component/convex.config.d.ts.map +0 -1
  148. package/dist/component/http.d.ts +0 -4
  149. package/dist/component/http.d.ts.map +0 -1
  150. package/dist/component/http.js +0 -113
  151. package/dist/component/http.js.map +0 -1
  152. package/dist/component/lib.d.ts.map +0 -1
  153. package/dist/component/schema.d.ts.map +0 -1
  154. package/dist/component/serving.d.ts +0 -23
  155. package/dist/component/serving.d.ts.map +0 -1
  156. package/dist/component/serving.js +0 -161
  157. package/dist/component/serving.js.map +0 -1
  158. package/dist/react/index.d.ts +0 -59
  159. package/dist/react/index.d.ts.map +0 -1
  160. package/dist/react/index.js +0 -137
  161. package/dist/react/index.js.map +0 -1
  162. package/src/cli/args.test.ts +0 -79
  163. package/src/cli/args.ts +0 -153
  164. package/src/cli/argumentChunks.test.ts +0 -27
  165. package/src/cli/argumentChunks.ts +0 -40
  166. package/src/cli/collectFiles.test.ts +0 -26
  167. package/src/cli/collectFiles.ts +0 -59
  168. package/src/cli/commands.ts +0 -68
  169. package/src/cli/componentName.test.ts +0 -41
  170. package/src/cli/componentName.ts +0 -46
  171. package/src/cli/deploy.ts +0 -336
  172. package/src/cli/deployEnvironment.test.ts +0 -27
  173. package/src/cli/deployEnvironment.ts +0 -17
  174. package/src/cli/index.ts +0 -65
  175. package/src/cli/upload.ts +0 -837
  176. package/src/client/_generated/_ignore.ts +0 -1
  177. package/src/client/index.test.ts +0 -549
  178. package/src/client/index.ts +0 -344
  179. package/src/client/setup.test.ts +0 -22
  180. package/src/component/_generated/api.ts +0 -54
  181. package/src/component/_generated/component.ts +0 -55
  182. package/src/component/_generated/dataModel.ts +0 -60
  183. package/src/component/_generated/server.ts +0 -156
  184. package/src/component/convex.config.ts +0 -3
  185. package/src/component/http.test.ts +0 -254
  186. package/src/component/http.ts +0 -133
  187. package/src/component/lib.test.ts +0 -1007
  188. package/src/component/lib.ts +0 -862
  189. package/src/component/schema.ts +0 -75
  190. package/src/component/serving.ts +0 -170
  191. package/src/component/setup.test.ts +0 -11
  192. package/src/react/index.tsx +0 -197
  193. package/src/test.ts +0 -23
@@ -0,0 +1,717 @@
1
+ import { FoundResponse, InternalServerErrorResponse, MethodNotAllowedResponse, MovedPermanentlyResponse, NoIntentResponse, NotFoundResponse, NotModifiedResponse, OkResponse, PermanentRedirectResponse, SeeOtherResponse, TemporaryRedirectResponse, } from "../../utils/responses.js";
2
+ import { mockJaegerBinding } from "../../utils/tracing.js";
3
+ import { flagIsEnabled, SEC_FETCH_MODE_NAVIGATE_HEADER_PREFERS_ASSET_SERVING, } from "./compatibility-flags.js";
4
+ import { addBasePath, stripBasePath } from "./utils/base-path.js";
5
+ import { attachCustomHeaders, getAssetHeaders } from "./utils/headers.js";
6
+ import { generateRedirectsMatcher, staticRedirectsMatcher, } from "./utils/rules-engine.js";
7
+ export const REDIRECTS_VERSION = 1;
8
+ export const HEADERS_VERSION = 2;
9
+ const getResponseOrAssetIntent = async (request, env, configuration, exists, originalDecodedPathname, analytics) => {
10
+ const url = new URL(request.url);
11
+ const { search } = url;
12
+ const base = configuration.base_path;
13
+ // The caller has already enforced the base against the original public
14
+ // pathname. Redirect matching keeps the full public pathname; a non-`200`
15
+ // authored redirect returns unchanged, while a `200` rewrite is checked
16
+ // again below as an authoritative public target.
17
+ const redirectResult = handleRedirects(env, request, configuration, url.host, url.pathname, search);
18
+ if (redirectResult instanceof Response) {
19
+ analytics?.setData({ servedBy: "redirect" });
20
+ return redirectResult;
21
+ }
22
+ const { proxied, pathname } = redirectResult;
23
+ // A `200` proxy rewrite defines the effective public path used for asset
24
+ // lookup. The original request has already passed the public entry-point
25
+ // boundary check, but the rewritten target must be checked independently.
26
+ const effectiveDecodedPathname = proxied
27
+ ? decodePath(pathname)
28
+ : originalDecodedPathname;
29
+ const { inPrefix, assetPath } = stripBasePath(effectiveDecodedPathname, base);
30
+ if (!inPrefix) {
31
+ return noIntent(env, analytics, configuration, effectiveDecodedPathname, proxied);
32
+ }
33
+ const baseRootIntent = proxied
34
+ ? null
35
+ : await getBaseRootIntent(effectiveDecodedPathname, base, request, configuration, exists);
36
+ const intent = baseRootIntent ??
37
+ (await getIntent(assetPath, request, configuration, exists));
38
+ if (!intent) {
39
+ return noIntent(env, analytics, configuration, effectiveDecodedPathname, proxied);
40
+ }
41
+ const method = request.method.toUpperCase();
42
+ if (!["GET", "HEAD"].includes(method)) {
43
+ analytics?.setData({ servedBy: "method-not-allowed" });
44
+ return env.JAEGER.enterSpan("method_not_allowed", (span) => {
45
+ span.setTags({
46
+ method,
47
+ status: MethodNotAllowedResponse.status,
48
+ });
49
+ return new MethodNotAllowedResponse();
50
+ });
51
+ }
52
+ // `intent.redirect` is an asset-relative location generated by the asset
53
+ // Worker; re-prefix it with the public base exactly once. The non-redirect
54
+ // fallback is the effective public pathname and must NOT be re-prefixed.
55
+ const decodedDestination = intent.redirect !== null
56
+ ? addBasePath(intent.redirect, base)
57
+ : effectiveDecodedPathname;
58
+ const encodedDestination = encodePath(decodedDestination);
59
+ /**
60
+ * The canonical path we serve an asset at is the decoded and re-encoded version.
61
+ * Thus we need to redirect if that is different from the decoded version.
62
+ * We combine this with other redirects (e.g. for html_handling) to avoid multiple redirects.
63
+ */
64
+ if ((encodedDestination !== pathname && intent.asset) ||
65
+ intent.redirect !== null) {
66
+ analytics?.setData({ servedBy: "redirect" });
67
+ return env.JAEGER.enterSpan("redirect", (span) => {
68
+ span.setTags({
69
+ originalPath: pathname,
70
+ location: encodedDestination !== pathname
71
+ ? encodedDestination
72
+ : (intent.redirect ?? "<unknown>"),
73
+ status: TemporaryRedirectResponse.status,
74
+ });
75
+ return new TemporaryRedirectResponse(encodedDestination + search);
76
+ });
77
+ }
78
+ if (!intent.asset) {
79
+ analytics?.setData({ servedBy: "error" });
80
+ return env.JAEGER.enterSpan("unknown_action", (span) => {
81
+ span.setTags({
82
+ pathname,
83
+ status: InternalServerErrorResponse.status,
84
+ });
85
+ return new InternalServerErrorResponse(new Error("Unknown action"));
86
+ });
87
+ }
88
+ return { ...intent.asset, resolver: intent.resolver };
89
+ };
90
+ /**
91
+ * Return the protocol-level response for a request the Asset Worker does not
92
+ * intend to serve, while recording the public decoded pathname in telemetry.
93
+ * Proxied rewrites are claimed as 404s unless the caller explicitly opts out.
94
+ */
95
+ function noIntent(env, analytics, configuration, decodedPathname, proxied, claimProxiedRequest = true) {
96
+ const response = proxied && claimProxiedRequest
97
+ ? new NotFoundResponse()
98
+ : new NoIntentResponse();
99
+ analytics?.setData({ servedBy: "none" });
100
+ return env.JAEGER.enterSpan("no_intent", (span) => {
101
+ span.setTags({
102
+ decodedPathname,
103
+ configuration: JSON.stringify(configuration),
104
+ proxied,
105
+ status: response.status,
106
+ });
107
+ return response;
108
+ });
109
+ }
110
+ const resolveAssetIntentToResponse = async (assetIntent, request, env, configuration, getByETag, analytics) => {
111
+ const { pathname } = new URL(request.url);
112
+ const method = request.method.toUpperCase();
113
+ const asset = await env.JAEGER.enterSpan("getByETag", async (span) => {
114
+ span.setTags({
115
+ pathname,
116
+ eTag: assetIntent.eTag,
117
+ status: assetIntent.status,
118
+ });
119
+ return await getByETag(assetIntent.eTag, request);
120
+ });
121
+ const headers = getAssetHeaders(assetIntent, asset.contentType, asset.cacheStatus, request, configuration);
122
+ analytics.setData({ cacheStatus: asset.cacheStatus });
123
+ const strongETag = `"${assetIntent.eTag}"`;
124
+ const weakETag = `W/${strongETag}`;
125
+ const ifNoneMatch = request.headers.get("If-None-Match") || "";
126
+ if ([weakETag, strongETag].includes(ifNoneMatch)) {
127
+ analytics.setData({ servedBy: "not-modified" });
128
+ return env.JAEGER.enterSpan("matched_etag", (span) => {
129
+ span.setTags({
130
+ matchedEtag: ifNoneMatch,
131
+ status: NotModifiedResponse.status,
132
+ });
133
+ return new NotModifiedResponse(null, { headers });
134
+ });
135
+ }
136
+ analytics.setData({
137
+ servedBy: servedByForResolver(assetIntent.resolver, configuration),
138
+ });
139
+ return env.JAEGER.enterSpan("response", (span) => {
140
+ span.setTags({
141
+ etag: assetIntent.eTag,
142
+ status: assetIntent.status,
143
+ head: method === "HEAD",
144
+ });
145
+ const body = method === "HEAD" ? null : asset.readableStream;
146
+ switch (assetIntent.status) {
147
+ case NotFoundResponse.status:
148
+ return new NotFoundResponse(body, { headers });
149
+ case OkResponse.status:
150
+ return new OkResponse(body, { headers });
151
+ }
152
+ });
153
+ };
154
+ /**
155
+ * Decode the request pathname exactly once and map it from the public URL
156
+ * namespace into the asset-directory namespace. The returned decodedPathname
157
+ * always remains public and is suitable for request-facing telemetry.
158
+ */
159
+ function getRequestBasePath(request, base) {
160
+ const decodedPathname = decodePath(new URL(request.url).pathname);
161
+ return {
162
+ decodedPathname,
163
+ ...stripBasePath(decodedPathname, base),
164
+ };
165
+ }
166
+ export const canFetch = async (request, env, configuration, exists) => {
167
+ const requestPath = getRequestBasePath(request, configuration.base_path);
168
+ if (!requestPath.inPrefix) {
169
+ return false;
170
+ }
171
+ const shouldKeepNotFoundHandling = configuration.has_static_routing ||
172
+ (flagIsEnabled(configuration, SEC_FETCH_MODE_NAVIGATE_HEADER_PREFERS_ASSET_SERVING) &&
173
+ request.headers.get("Sec-Fetch-Mode") === "navigate");
174
+ if (!shouldKeepNotFoundHandling) {
175
+ configuration = {
176
+ ...configuration,
177
+ not_found_handling: "none",
178
+ };
179
+ }
180
+ const responseOrAssetIntent = await getResponseOrAssetIntent(request, env, configuration, exists, requestPath.decodedPathname);
181
+ if (responseOrAssetIntent instanceof NoIntentResponse) {
182
+ return false;
183
+ }
184
+ return true;
185
+ };
186
+ export const handleRequest = async (request, env, configuration, exists, getByETag, analytics) => {
187
+ const requestPath = getRequestBasePath(request, configuration.base_path);
188
+ if (!requestPath.inPrefix) {
189
+ return noIntent(env, analytics, configuration, requestPath.decodedPathname, false);
190
+ }
191
+ const responseOrAssetIntent = await getResponseOrAssetIntent(request, env, configuration, exists, requestPath.decodedPathname, analytics);
192
+ const response = responseOrAssetIntent instanceof Response
193
+ ? responseOrAssetIntent
194
+ : await resolveAssetIntentToResponse(responseOrAssetIntent, request, env, configuration, getByETag, analytics);
195
+ return attachCustomHeaders(request, response, configuration, env);
196
+ };
197
+ function servedByForResolver(resolver, configuration) {
198
+ if (resolver === "html-handling") {
199
+ return "asset";
200
+ }
201
+ switch (configuration.not_found_handling) {
202
+ case "single-page-application":
203
+ return "spa";
204
+ case "404-page":
205
+ return "404-page";
206
+ case "none":
207
+ return "none";
208
+ default:
209
+ configuration.not_found_handling;
210
+ return "error";
211
+ }
212
+ }
213
+ // TODO: Trace this
214
+ export const getIntent = async (pathname, request, configuration, exists, skipRedirects = false) => {
215
+ switch (configuration.html_handling) {
216
+ case "auto-trailing-slash": {
217
+ return htmlHandlingAutoTrailingSlash(pathname, request, configuration, exists, skipRedirects);
218
+ }
219
+ case "force-trailing-slash": {
220
+ return htmlHandlingForceTrailingSlash(pathname, request, configuration, exists, skipRedirects);
221
+ }
222
+ case "drop-trailing-slash": {
223
+ return htmlHandlingDropTrailingSlash(pathname, request, configuration, exists, skipRedirects);
224
+ }
225
+ case "none": {
226
+ return htmlHandlingNone(pathname, request, configuration, exists);
227
+ }
228
+ }
229
+ };
230
+ /**
231
+ * Resolve the generated redirect, if any, for the public base root. The
232
+ * returned redirect is asset-relative and must be prefixed exactly once by the
233
+ * top-level handler. Authored redirects and proxy rewrites are handled before
234
+ * this helper is called.
235
+ */
236
+ async function getBaseRootIntent(publicDecodedPath, base, request, configuration, exists) {
237
+ let pathname;
238
+ switch (configuration.html_handling) {
239
+ case "force-trailing-slash":
240
+ case "auto-trailing-slash":
241
+ pathname =
242
+ base !== "/" && publicDecodedPath === base.slice(0, -1) ? "/" : null;
243
+ break;
244
+ case "drop-trailing-slash":
245
+ pathname = base !== "/" && publicDecodedPath === base ? "" : null;
246
+ break;
247
+ case "none":
248
+ return null;
249
+ }
250
+ if (pathname === null || !(await exists("/index.html", request))) {
251
+ return null;
252
+ }
253
+ return {
254
+ asset: null,
255
+ redirect: pathname,
256
+ resolver: "html-handling",
257
+ };
258
+ }
259
+ const htmlHandlingAutoTrailingSlash = async (pathname, request, configuration, exists, skipRedirects) => {
260
+ let redirectResult = null;
261
+ let eTagResult = null;
262
+ const exactETag = await exists(pathname, request);
263
+ if (pathname.endsWith("/index")) {
264
+ if (exactETag) {
265
+ // there's a binary /index file
266
+ return {
267
+ asset: {
268
+ eTag: exactETag,
269
+ status: OkResponse.status,
270
+ },
271
+ redirect: null,
272
+ resolver: "html-handling",
273
+ };
274
+ }
275
+ else {
276
+ if ((redirectResult = await safeRedirect(`${pathname}.html`, request, pathname.slice(0, -"index".length), configuration, exists, skipRedirects, "html-handling"))) {
277
+ // /foo/index.html exists so redirect to /foo/
278
+ return redirectResult;
279
+ }
280
+ else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/index".length)}.html`, request, pathname.slice(0, -"/index".length), configuration, exists, skipRedirects, "html-handling"))) {
281
+ // /foo.html exists so redirect to /foo
282
+ return redirectResult;
283
+ }
284
+ }
285
+ }
286
+ else if (pathname.endsWith("/index.html")) {
287
+ if ((redirectResult = await safeRedirect(pathname, request, pathname.slice(0, -"index.html".length), configuration, exists, skipRedirects, "html-handling"))) {
288
+ // /foo/index.html exists so redirect to /foo/
289
+ return redirectResult;
290
+ }
291
+ else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/index.html".length)}.html`, request, pathname.slice(0, -"/index.html".length), configuration, exists, skipRedirects, "html-handling"))) {
292
+ // /foo.html exists so redirect to /foo
293
+ return redirectResult;
294
+ }
295
+ }
296
+ else if (pathname.endsWith("/")) {
297
+ if ((eTagResult = await exists(`${pathname}index.html`, request))) {
298
+ // /foo/index.html exists so serve at /foo/
299
+ return {
300
+ asset: { eTag: eTagResult, status: OkResponse.status },
301
+ redirect: null,
302
+ resolver: "html-handling",
303
+ };
304
+ }
305
+ else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/".length)}.html`, request, pathname.slice(0, -"/".length), configuration, exists, skipRedirects, "html-handling"))) {
306
+ // /foo.html exists so redirect to /foo
307
+ return redirectResult;
308
+ }
309
+ }
310
+ else if (pathname.endsWith(".html")) {
311
+ if ((redirectResult = await safeRedirect(pathname, request, pathname.slice(0, -".html".length), configuration, exists, skipRedirects, "html-handling"))) {
312
+ // /foo.html exists so redirect to /foo
313
+ return redirectResult;
314
+ }
315
+ else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -".html".length)}/index.html`, request, `${pathname.slice(0, -".html".length)}/`, configuration, exists, skipRedirects, "html-handling"))) {
316
+ // request for /foo.html but /foo/index.html exists so redirect to /foo/
317
+ return redirectResult;
318
+ }
319
+ }
320
+ if (exactETag) {
321
+ // there's a binary /foo file
322
+ return {
323
+ asset: { eTag: exactETag, status: OkResponse.status },
324
+ redirect: null,
325
+ resolver: "html-handling",
326
+ };
327
+ }
328
+ else if ((eTagResult = await exists(`${pathname}.html`, request))) {
329
+ // foo.html exists so serve at /foo
330
+ return {
331
+ asset: { eTag: eTagResult, status: OkResponse.status },
332
+ redirect: null,
333
+ resolver: "html-handling",
334
+ };
335
+ }
336
+ else if ((redirectResult = await safeRedirect(`${pathname}/index.html`, request, `${pathname}/`, configuration, exists, skipRedirects, "html-handling"))) {
337
+ // /foo/index.html exists so redirect to /foo/
338
+ return redirectResult;
339
+ }
340
+ return notFound(pathname, request, configuration, exists);
341
+ };
342
+ const htmlHandlingForceTrailingSlash = async (pathname, request, configuration, exists, skipRedirects) => {
343
+ let redirectResult = null;
344
+ let eTagResult = null;
345
+ const exactETag = await exists(pathname, request);
346
+ if (pathname.endsWith("/index")) {
347
+ if (exactETag) {
348
+ // there's a binary /index file
349
+ return {
350
+ asset: { eTag: exactETag, status: OkResponse.status },
351
+ redirect: null,
352
+ resolver: "html-handling",
353
+ };
354
+ }
355
+ else {
356
+ if ((redirectResult = await safeRedirect(`${pathname}.html`, request, pathname.slice(0, -"index".length), configuration, exists, skipRedirects, "html-handling"))) {
357
+ // /foo/index.html exists so redirect to /foo/
358
+ return redirectResult;
359
+ }
360
+ else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/index".length)}.html`, request, pathname.slice(0, -"index".length), configuration, exists, skipRedirects, "html-handling"))) {
361
+ // /foo.html exists so redirect to /foo/
362
+ return redirectResult;
363
+ }
364
+ }
365
+ }
366
+ else if (pathname.endsWith("/index.html")) {
367
+ if ((redirectResult = await safeRedirect(pathname, request, pathname.slice(0, -"index.html".length), configuration, exists, skipRedirects, "html-handling"))) {
368
+ // /foo/index.html exists so redirect to /foo/
369
+ return redirectResult;
370
+ }
371
+ else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/index.html".length)}.html`, request, pathname.slice(0, -"index.html".length), configuration, exists, skipRedirects, "html-handling"))) {
372
+ // /foo.html exists so redirect to /foo/
373
+ return redirectResult;
374
+ }
375
+ }
376
+ else if (pathname.endsWith("/")) {
377
+ if ((eTagResult = await exists(`${pathname}index.html`, request))) {
378
+ // /foo/index.html exists so serve at /foo/
379
+ return {
380
+ asset: { eTag: eTagResult, status: OkResponse.status },
381
+ redirect: null,
382
+ resolver: "html-handling",
383
+ };
384
+ }
385
+ else if ((eTagResult = await exists(`${pathname.slice(0, -"/".length)}.html`, request))) {
386
+ // /foo.html exists so serve at /foo/
387
+ return {
388
+ asset: { eTag: eTagResult, status: OkResponse.status },
389
+ redirect: null,
390
+ resolver: "html-handling",
391
+ };
392
+ }
393
+ }
394
+ else if (pathname.endsWith(".html")) {
395
+ if ((redirectResult = await safeRedirect(pathname, request, `${pathname.slice(0, -".html".length)}/`, configuration, exists, skipRedirects, "html-handling"))) {
396
+ // /foo.html exists so redirect to /foo/
397
+ return redirectResult;
398
+ }
399
+ else if (exactETag) {
400
+ // there's both /foo.html and /foo/index.html so we serve /foo.html at /foo.html only
401
+ return {
402
+ asset: { eTag: exactETag, status: OkResponse.status },
403
+ redirect: null,
404
+ resolver: "html-handling",
405
+ };
406
+ }
407
+ else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -".html".length)}/index.html`, request, `${pathname.slice(0, -".html".length)}/`, configuration, exists, skipRedirects, "html-handling"))) {
408
+ // /foo/index.html exists so redirect to /foo/
409
+ return redirectResult;
410
+ }
411
+ }
412
+ if (exactETag) {
413
+ // there's a binary /foo file
414
+ return {
415
+ asset: { eTag: exactETag, status: OkResponse.status },
416
+ redirect: null,
417
+ resolver: "html-handling",
418
+ };
419
+ }
420
+ else if ((redirectResult = await safeRedirect(`${pathname}.html`, request, `${pathname}/`, configuration, exists, skipRedirects, "html-handling"))) {
421
+ // /foo.html exists so redirect to /foo/
422
+ return redirectResult;
423
+ }
424
+ else if ((redirectResult = await safeRedirect(`${pathname}/index.html`, request, `${pathname}/`, configuration, exists, skipRedirects, "html-handling"))) {
425
+ // /foo/index.html exists so redirect to /foo/
426
+ return redirectResult;
427
+ }
428
+ return notFound(pathname, request, configuration, exists);
429
+ };
430
+ const htmlHandlingDropTrailingSlash = async (pathname, request, configuration, exists, skipRedirects) => {
431
+ let redirectResult = null;
432
+ let eTagResult = null;
433
+ const exactETag = await exists(pathname, request);
434
+ if (pathname.endsWith("/index")) {
435
+ if (exactETag) {
436
+ // there's a binary /index file
437
+ return {
438
+ asset: { eTag: exactETag, status: OkResponse.status },
439
+ redirect: null,
440
+ resolver: "html-handling",
441
+ };
442
+ }
443
+ else {
444
+ if (pathname === "/index") {
445
+ if ((redirectResult = await safeRedirect("/index.html", request, "/", configuration, exists, skipRedirects, "html-handling"))) {
446
+ return redirectResult;
447
+ }
448
+ }
449
+ else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/index".length)}.html`, request, pathname.slice(0, -"/index".length), configuration, exists, skipRedirects, "html-handling"))) {
450
+ // /foo.html exists so redirect to /foo
451
+ return redirectResult;
452
+ }
453
+ else if ((redirectResult = await safeRedirect(`${pathname}.html`, request, pathname.slice(0, -"/index".length), configuration, exists, skipRedirects, "html-handling"))) {
454
+ // /foo/index.html exists so redirect to /foo
455
+ return redirectResult;
456
+ }
457
+ }
458
+ }
459
+ else if (pathname.endsWith("/index.html")) {
460
+ // special handling so you don't drop / if the path is just /
461
+ if (pathname === "/index.html") {
462
+ if ((redirectResult = await safeRedirect("/index.html", request, "/", configuration, exists, skipRedirects, "html-handling"))) {
463
+ return redirectResult;
464
+ }
465
+ }
466
+ else if ((redirectResult = await safeRedirect(pathname, request, pathname.slice(0, -"/index.html".length), configuration, exists, skipRedirects, "html-handling"))) {
467
+ // /foo/index.html exists so redirect to /foo
468
+ return redirectResult;
469
+ }
470
+ else if (exactETag) {
471
+ // there's both /foo.html and /foo/index.html so we serve /foo/index.html at /foo/index.html only
472
+ return {
473
+ asset: { eTag: exactETag, status: OkResponse.status },
474
+ redirect: null,
475
+ resolver: "html-handling",
476
+ };
477
+ }
478
+ else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/index.html".length)}.html`, request, pathname.slice(0, -"/index.html".length), configuration, exists, skipRedirects, "html-handling"))) {
479
+ // /foo.html exists so redirect to /foo
480
+ return redirectResult;
481
+ }
482
+ }
483
+ else if (pathname.endsWith("/")) {
484
+ if (pathname === "/") {
485
+ if ((eTagResult = await exists("/index.html", request))) {
486
+ // /index.html exists so serve at /
487
+ return {
488
+ asset: { eTag: eTagResult, status: OkResponse.status },
489
+ redirect: null,
490
+ resolver: "html-handling",
491
+ };
492
+ }
493
+ }
494
+ else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/".length)}.html`, request, pathname.slice(0, -"/".length), configuration, exists, skipRedirects, "html-handling"))) {
495
+ // /foo.html exists so redirect to /foo
496
+ return redirectResult;
497
+ }
498
+ else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -"/".length)}/index.html`, request, pathname.slice(0, -"/".length), configuration, exists, skipRedirects, "html-handling"))) {
499
+ // /foo/index.html exists so redirect to /foo
500
+ return redirectResult;
501
+ }
502
+ }
503
+ else if (pathname.endsWith(".html")) {
504
+ if ((redirectResult = await safeRedirect(pathname, request, pathname.slice(0, -".html".length), configuration, exists, skipRedirects, "html-handling"))) {
505
+ // /foo.html exists so redirect to /foo
506
+ return redirectResult;
507
+ }
508
+ else if ((redirectResult = await safeRedirect(`${pathname.slice(0, -".html".length)}/index.html`, request, pathname.slice(0, -".html".length), configuration, exists, skipRedirects, "html-handling"))) {
509
+ // /foo/index.html exists so redirect to /foo
510
+ return redirectResult;
511
+ }
512
+ }
513
+ if (exactETag) {
514
+ // there's a binary /foo file
515
+ return {
516
+ asset: { eTag: exactETag, status: OkResponse.status },
517
+ redirect: null,
518
+ resolver: "html-handling",
519
+ };
520
+ }
521
+ else if ((eTagResult = await exists(`${pathname}.html`, request))) {
522
+ // /foo.html exists so serve at /foo
523
+ return {
524
+ asset: { eTag: eTagResult, status: OkResponse.status },
525
+ redirect: null,
526
+ resolver: "html-handling",
527
+ };
528
+ }
529
+ else if ((eTagResult = await exists(`${pathname}/index.html`, request))) {
530
+ // /foo/index.html exists so serve at /foo
531
+ return {
532
+ asset: { eTag: eTagResult, status: OkResponse.status },
533
+ redirect: null,
534
+ resolver: "html-handling",
535
+ };
536
+ }
537
+ return notFound(pathname, request, configuration, exists);
538
+ };
539
+ const htmlHandlingNone = async (pathname, request, configuration, exists) => {
540
+ const exactETag = await exists(pathname, request);
541
+ if (exactETag) {
542
+ return {
543
+ asset: { eTag: exactETag, status: OkResponse.status },
544
+ redirect: null,
545
+ resolver: "html-handling",
546
+ };
547
+ }
548
+ else {
549
+ return notFound(pathname, request, configuration, exists);
550
+ }
551
+ };
552
+ const notFound = async (pathname, request, configuration, exists) => {
553
+ switch (configuration.not_found_handling) {
554
+ case "single-page-application": {
555
+ const eTag = await exists("/index.html", request);
556
+ if (eTag) {
557
+ return {
558
+ asset: { eTag, status: OkResponse.status },
559
+ redirect: null,
560
+ resolver: "not-found",
561
+ };
562
+ }
563
+ return null;
564
+ }
565
+ case "404-page": {
566
+ let cwd = pathname;
567
+ while (cwd) {
568
+ cwd = cwd.slice(0, cwd.lastIndexOf("/"));
569
+ const eTag = await exists(`${cwd}/404.html`, request);
570
+ if (eTag) {
571
+ return {
572
+ asset: { eTag, status: NotFoundResponse.status },
573
+ redirect: null,
574
+ resolver: "not-found",
575
+ };
576
+ }
577
+ }
578
+ return null;
579
+ }
580
+ case "none":
581
+ default: {
582
+ return null;
583
+ }
584
+ }
585
+ };
586
+ const safeRedirect = async (file, request, destination, configuration, exists, skip, resolver) => {
587
+ if (skip) {
588
+ return null;
589
+ }
590
+ if (!(await exists(destination, request))) {
591
+ const intent = await getIntent(destination, request, configuration, exists, true);
592
+ // return only if the eTag matches - i.e. not the 404 case
593
+ if (intent?.asset && intent.asset.eTag === (await exists(file, request))) {
594
+ return {
595
+ asset: null,
596
+ redirect: destination,
597
+ resolver,
598
+ };
599
+ }
600
+ }
601
+ return null;
602
+ };
603
+ /**
604
+ *
605
+ * +===========================================+===========+======================+
606
+ * | character type | fetch() | encodeURIComponent() |
607
+ * +===========================================+===========+======================+
608
+ * | unreserved ASCII e.g. a-z | unchanged | unchanged |
609
+ * +-------------------------------------------+-----------+----------------------+
610
+ * | reserved (sometimes encoded) | unchanged | encoded |
611
+ * | e.g. [ ] @ $ ! ' ( ) * + , ; = : ? # & % | | |
612
+ * +-------------------------------------------+-----------+----------------------+
613
+ * | non-ASCII e.g. ü. and space | encoded | encoded |
614
+ * +-------------------------------------------+-----------+----------------------+
615
+ *
616
+ * 1. Decode incoming path to handle non-ASCII characters or optionally encoded characters (e.g. square brackets)
617
+ * 2. Match decoded path to manifest
618
+ * 3. Re-encode the path and redirect if the re-encoded path is different from the original path
619
+ *
620
+ * If the user uploads a file that is already URL-encoded, that is accessible only at the (double) encoded path.
621
+ * e.g. /%5Bboop%5D.html is served at /%255Bboop%255D only
622
+ *
623
+ * */
624
+ /**
625
+ * Decode all incoming paths to ensure that we can handle paths with non-ASCII characters.
626
+ */
627
+ const decodePath = (pathname) => {
628
+ return (pathname
629
+ .split("/")
630
+ .map((x) => {
631
+ try {
632
+ const decoded = decodeURIComponent(x);
633
+ return decoded;
634
+ }
635
+ catch {
636
+ return x;
637
+ }
638
+ })
639
+ .join("/")
640
+ // normalize the path; remove multiple slashes which could lead to same-schema redirects
641
+ .replace(/\/+/g, "/"));
642
+ };
643
+ /**
644
+ * Use the encoded path as the canonical path for sometimes-encoded characters
645
+ * e.g. /[boop] -> /%5Bboop%5D 307
646
+ */
647
+ const encodePath = (pathname) => {
648
+ return pathname
649
+ .split("/")
650
+ .map((x) => {
651
+ try {
652
+ const encoded = encodeURIComponent(x);
653
+ return encoded;
654
+ }
655
+ catch {
656
+ return x;
657
+ }
658
+ })
659
+ .join("/");
660
+ };
661
+ const handleRedirects = (env, request, configuration, host, pathname, search) => {
662
+ const jaeger = env.JAEGER ?? mockJaegerBinding();
663
+ return jaeger.enterSpan("handle_redirects", (span) => {
664
+ const redirectMatch = staticRedirectsMatcher(configuration, host, pathname) ||
665
+ generateRedirectsMatcher(configuration)({ request })[0];
666
+ let proxied = false;
667
+ if (redirectMatch) {
668
+ if (redirectMatch.status === 200) {
669
+ // A 200 redirect means that we are proxying/rewriting to a different asset, for example,
670
+ // a request with url /users/12345 could be pointed to /users/id.html. In order to
671
+ // do this, we overwrite the pathname, and instead match for assets with that url,
672
+ // and importantly, do not use the regular redirect handler - as the url visible to
673
+ // the user does not change
674
+ pathname = new URL(redirectMatch.to, request.url).pathname;
675
+ proxied = true;
676
+ span.setTags({
677
+ matched: true,
678
+ proxied: true,
679
+ new_path: pathname,
680
+ status: redirectMatch.status,
681
+ });
682
+ }
683
+ else {
684
+ const { status, to } = redirectMatch;
685
+ const destination = new URL(to, request.url);
686
+ const location = destination.origin === new URL(request.url).origin
687
+ ? `${destination.pathname}${destination.search || search}${destination.hash}`
688
+ : `${destination.href.slice(0, destination.href.length - (destination.search.length + destination.hash.length))}${destination.search ? destination.search : search}${destination.hash}`;
689
+ span.setTags({
690
+ matched: true,
691
+ destination: location,
692
+ status,
693
+ });
694
+ switch (status) {
695
+ case MovedPermanentlyResponse.status:
696
+ return new MovedPermanentlyResponse(location);
697
+ case SeeOtherResponse.status:
698
+ return new SeeOtherResponse(location);
699
+ case TemporaryRedirectResponse.status:
700
+ return new TemporaryRedirectResponse(location);
701
+ case PermanentRedirectResponse.status:
702
+ return new PermanentRedirectResponse(location);
703
+ case FoundResponse.status:
704
+ default:
705
+ return new FoundResponse(location);
706
+ }
707
+ }
708
+ }
709
+ else {
710
+ span.setTags({
711
+ matched: false,
712
+ });
713
+ }
714
+ return { proxied, pathname };
715
+ });
716
+ };
717
+ //# sourceMappingURL=handler.js.map