ngx-cloudflare 0.2.1 → 0.3.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.
- package/CHANGELOG.md +67 -0
- package/README.md +4 -2
- package/fesm2022/ngx-cloudflare-server.mjs +34 -10
- package/fesm2022/ngx-cloudflare-server.mjs.map +1 -1
- package/fesm2022/ngx-cloudflare-testing.mjs +7 -7
- package/fesm2022/ngx-cloudflare-testing.mjs.map +1 -1
- package/fesm2022/ngx-cloudflare.mjs +11 -11
- package/fesm2022/ngx-cloudflare.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ngx-cloudflare-server.d.ts +12 -10
- package/types/ngx-cloudflare-testing.d.ts +1 -1
- package/types/ngx-cloudflare.d.ts +2 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `ngx-cloudflare` package. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses [Semantic Versioning](https://semver.org/). Until 1.0.0, a minor version may rename or remove public functions.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.3.0] - 2026-11-13
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `cachePages` option: stores rendered pages in the Workers Cache API when their `Cache-Control` has a shared max-age, skipping requests with cookies or `Authorization`.
|
|
12
|
+
- `isCacheablePageRequest(request)` and `isCacheablePageResponse(response)` helpers.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Internal exports now start with `ɵ` and may change in any release: `ɵCloudflareFetchBackend`, `ɵisCloudflareContext`, `ɵtoApiHandler`, `ɵcallInProcess`, `ɵCLOUDFLARE_CONTEXT_MARKER`.
|
|
17
|
+
- `createTestExecutionContext` is renamed to `createCloudflareTestExecutionContext`, to match `createCloudflareTestContext`.
|
|
18
|
+
- Durable Objects recipe: a second layout keeps the class in its own Worker, bound by `script_name`, so the binding also works in `ng serve`.
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
- Exports nothing used: `hostRefusal`, `isProxyHeader`, `DEFAULT_ALLOWED_HOSTS`, `DEFAULT_API_PREFIX`, `ALLOWED_HOSTS_VAR`.
|
|
23
|
+
|
|
24
|
+
## [0.2.2] - 2026-11-12
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- `CHANGELOG.md`, also shipped in the npm package.
|
|
29
|
+
- The ngx-cloudflare logo at the top of the README.
|
|
30
|
+
|
|
31
|
+
## [0.2.1] - 2026-11-11
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- README: the note now says this is an early 0.x release instead of "not on npm yet".
|
|
36
|
+
|
|
37
|
+
## [0.2.0] - 2026-11-11
|
|
38
|
+
|
|
39
|
+
First public release.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- `ngx-cloudflare/server`:
|
|
44
|
+
- `createCloudflareRequestHandler(options?)`, the Worker `fetch` handler for Angular SSR. It passes `env`, `ctx` and `request.cf` to Angular, and fills them in under `ng serve` through wrangler's platform proxy.
|
|
45
|
+
- A hostname allow-list that works on Workers: `localhost`, `127.0.0.1`, `*.workers.dev`, `allowedHosts` from code and the `ALLOWED_HOSTS` var. Other hosts get a 400 that says where to add them.
|
|
46
|
+
- An `api` option for requests under `/api/`, and `dispatch`, which answers them in-process (falling back to the `ASSETS` binding) instead of a network self-fetch that Cloudflare rejects with error 1042.
|
|
47
|
+
- Removal of `forwarded` and `x-forwarded-*` headers before Angular sees them.
|
|
48
|
+
- `createCloudflareFetch` and helpers: `resolveAllowedHosts`, `isHostAllowed`, `stripProxyHeaders`, `isCloudflareContext`, `hostsFromEnv`, `toApiHandler`, `callInProcess`, `isInProcessRequest`.
|
|
49
|
+
- `ngx-cloudflare`:
|
|
50
|
+
- `injectCloudflare`, `injectEnv`, `injectExecutionContext` and `injectRequestCf`, each with `{ required: true }`.
|
|
51
|
+
- `provideCloudflare()`, which answers same-origin `HttpClient` calls in-process during rendering while keeping Angular's transfer cache working.
|
|
52
|
+
- `ngx-cloudflare/testing`: `provideCloudflareTesting`, `createCloudflareTestContext` and `createTestExecutionContext` for TestBed.
|
|
53
|
+
- `ng add ngx-cloudflare` for apps made by `ng new --ssr`, `npm create cloudflare` and `wrangler setup`:
|
|
54
|
+
- sets the build to `outputMode: server` and the neutral SSR platform, and keeps `wrangler` out of the bundle
|
|
55
|
+
- replaces a stock `src/server.ts`
|
|
56
|
+
- copies wrangler route hosts into `ALLOWED_HOSTS`
|
|
57
|
+
- creates or repairs `wrangler.jsonc`
|
|
58
|
+
- switches the stock prerender route to server rendering
|
|
59
|
+
- converts Angular 19 apps without server routing
|
|
60
|
+
- adds `preview`, `deploy` and `cf-typegen` scripts
|
|
61
|
+
- Support for Angular 19 to 22.
|
|
62
|
+
|
|
63
|
+
[Unreleased]: https://github.com/erkamyaman/ngx-cloudflare/compare/4721f62...HEAD
|
|
64
|
+
[0.3.0]: https://www.npmjs.com/package/ngx-cloudflare/v/0.3.0
|
|
65
|
+
[0.2.2]: https://www.npmjs.com/package/ngx-cloudflare/v/0.2.2
|
|
66
|
+
[0.2.1]: https://www.npmjs.com/package/ngx-cloudflare/v/0.2.1
|
|
67
|
+
[0.2.0]: https://www.npmjs.com/package/ngx-cloudflare/v/0.2.0
|
package/README.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<p align="center"><img src="https://raw.githubusercontent.com/erkamyaman/ngx-cloudflare/main/docs/logo/ngx-cloudflare-logo.svg" width="96" height="96" alt="" /></p>
|
|
1
2
|
<h1 align="center">ngx-cloudflare</h1>
|
|
2
3
|
<p align="center">
|
|
3
4
|
<img src="https://img.shields.io/maintenance/yes/2026?style=for-the-badge" alt="Maintained: yes" />
|
|
@@ -16,6 +17,7 @@ Workers bindings, a working hostname allow-list and in-process API calls for Ang
|
|
|
16
17
|
<td align="center"><a href="https://github.com/erkamyaman/ngx-cloudflare/blob/main/docs/api.md"><strong>API</strong></a></td>
|
|
17
18
|
<td align="center"><a href="https://github.com/erkamyaman/ngx-cloudflare/blob/main/docs/recipes.md">Recipes</a></td>
|
|
18
19
|
<td align="center"><a href="https://github.com/erkamyaman/ngx-cloudflare/blob/main/docs/troubleshooting.md">Troubleshooting</a></td>
|
|
20
|
+
<td align="center"><a href="https://github.com/erkamyaman/ngx-cloudflare/blob/main/CHANGELOG.md">Changelog</a></td>
|
|
19
21
|
<td align="center"><a href="#the-demo">Demo</a></td>
|
|
20
22
|
</tr>
|
|
21
23
|
</table>
|
|
@@ -150,9 +152,10 @@ These ship with the package too, for cases the core flow does not cover:
|
|
|
150
152
|
|
|
151
153
|
- `ngx-cloudflare/testing`: fake bindings for TestBed.
|
|
152
154
|
- `isInProcessRequest(request)`: lets an `api` handler tell a render-time call from a browser call.
|
|
155
|
+
- `cachePages: true`: keeps pages with a shared `Cache-Control` max-age in the Workers cache. See [Caching pages](https://github.com/erkamyaman/ngx-cloudflare/blob/main/docs/recipes.md#caching-pages).
|
|
153
156
|
- `dispatch` falls back to the `ASSETS` binding, so a page can read its own static JSON files in-process.
|
|
154
157
|
- Options to turn parts off: `apiPrefix: false`, `stripProxyHeaders: false`, `devPlatform: false`, `provideCloudflare({ inProcessRequests: false })`.
|
|
155
|
-
- A [Durable Objects recipe](https://github.com/erkamyaman/ngx-cloudflare/blob/main/docs/recipes.md#durable-objects)
|
|
158
|
+
- A [Durable Objects recipe](https://github.com/erkamyaman/ngx-cloudflare/blob/main/docs/recipes.md#durable-objects), including a layout that works in `ng serve`.
|
|
156
159
|
|
|
157
160
|
## Limits
|
|
158
161
|
|
|
@@ -164,7 +167,6 @@ These ship with the package too, for cases the core flow does not cover:
|
|
|
164
167
|
|
|
165
168
|
| Next | Why |
|
|
166
169
|
| --- | --- |
|
|
167
|
-
| Durable Objects in `ng serve` | The recipe works in `wrangler dev` and on Workers, but the platform proxy cannot run an internal Durable Object. |
|
|
168
170
|
| Workers beside an existing Node server | A separate build configuration, so adopting this does not replace a working deployment. |
|
|
169
171
|
| Bindings during prerendering | Build-time rendering has no request to carry them. |
|
|
170
172
|
| Typed helpers for queues and cron | `scheduled` and `queue` exports already work next to `fetch`; helpers would add types. |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AngularAppEngine, createRequestHandler } from "@angular/ssr";
|
|
2
|
-
const CLOUDFLARE_CONTEXT_MARKER = Symbol.for("ngx-cloudflare.context");
|
|
2
|
+
const ɵCLOUDFLARE_CONTEXT_MARKER = Symbol.for("ngx-cloudflare.context");
|
|
3
3
|
const DEFAULT_ALLOWED_HOSTS = [
|
|
4
4
|
"localhost",
|
|
5
5
|
"127.0.0.1",
|
|
@@ -11,8 +11,8 @@ const inProcessRequests = /* @__PURE__ */ new WeakSet();
|
|
|
11
11
|
function isInProcessRequest(request) {
|
|
12
12
|
return inProcessRequests.has(request);
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
return typeof value === "object" && value !== null && value[CLOUDFLARE_CONTEXT_MARKER] === true;
|
|
14
|
+
function ɵisCloudflareContext(value) {
|
|
15
|
+
return typeof value === "object" && value !== null && value[ɵCLOUDFLARE_CONTEXT_MARKER] === true;
|
|
16
16
|
}
|
|
17
17
|
const ALLOWED_HOSTS_VAR = "ALLOWED_HOSTS";
|
|
18
18
|
function hostsFromEnv(env) {
|
|
@@ -59,7 +59,19 @@ function stripProxyHeaders(request) {
|
|
|
59
59
|
for (const name of names) headers.delete(name);
|
|
60
60
|
return new Request(request, { headers });
|
|
61
61
|
}
|
|
62
|
-
function
|
|
62
|
+
function defaultPageCache() {
|
|
63
|
+
return globalThis.caches?.default;
|
|
64
|
+
}
|
|
65
|
+
function isCacheablePageRequest(request) {
|
|
66
|
+
return request.method === "GET" && !request.headers.has("authorization") && !request.headers.has("cookie");
|
|
67
|
+
}
|
|
68
|
+
function isCacheablePageResponse(response) {
|
|
69
|
+
if (response.status !== 200 || response.headers.has("set-cookie")) return false;
|
|
70
|
+
const cacheControl = response.headers.get("cache-control")?.toLowerCase() ?? "";
|
|
71
|
+
if (/(?:^|,)\s*(?:private|no-store|no-cache)\b/.test(cacheControl)) return false;
|
|
72
|
+
return /(?:^|,)\s*(?:s-maxage|max-age)\s*=\s*0*[1-9]/.test(cacheControl);
|
|
73
|
+
}
|
|
74
|
+
function ɵtoApiHandler(api) {
|
|
63
75
|
if (!api) return;
|
|
64
76
|
if (typeof api === "function") return api;
|
|
65
77
|
return (request, env, ctx) => api.fetch(request, env, ctx);
|
|
@@ -70,7 +82,7 @@ function matchesPrefix(request, prefix) {
|
|
|
70
82
|
const bare = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
71
83
|
return pathname === bare || pathname.startsWith(bare + "/");
|
|
72
84
|
}
|
|
73
|
-
async function
|
|
85
|
+
async function ɵcallInProcess(handler, request, env, ctx) {
|
|
74
86
|
inProcessRequests.add(request);
|
|
75
87
|
return await handler(request, env, ctx) ?? null;
|
|
76
88
|
}
|
|
@@ -90,9 +102,10 @@ function createCloudflareFetch(render, options = {}, loadDevPlatform) {
|
|
|
90
102
|
}
|
|
91
103
|
return hosts;
|
|
92
104
|
};
|
|
93
|
-
const api =
|
|
94
|
-
const apiPrefix = options.apiPrefix ??
|
|
105
|
+
const api = ɵtoApiHandler(options.api);
|
|
106
|
+
const apiPrefix = options.apiPrefix ?? DEFAULT_API_PREFIX;
|
|
95
107
|
const strip = options.stripProxyHeaders ?? true;
|
|
108
|
+
const cachePages = options.cachePages ?? false;
|
|
96
109
|
const loggedRefusals = /* @__PURE__ */ new Set();
|
|
97
110
|
async function callApi(request, platform) {
|
|
98
111
|
if (!api || !matchesPrefix(request, apiPrefix)) return null;
|
|
@@ -114,7 +127,7 @@ function createCloudflareFetch(render, options = {}, loadDevPlatform) {
|
|
|
114
127
|
inProcessRequests.add(request);
|
|
115
128
|
return await callApi(request, platform) ?? await serveAsset(request, platform);
|
|
116
129
|
},
|
|
117
|
-
[CLOUDFLARE_CONTEXT_MARKER]: true
|
|
130
|
+
[ɵCLOUDFLARE_CONTEXT_MARKER]: true
|
|
118
131
|
};
|
|
119
132
|
}
|
|
120
133
|
return async (request, env, ctx) => {
|
|
@@ -141,8 +154,19 @@ function createCloudflareFetch(render, options = {}, loadDevPlatform) {
|
|
|
141
154
|
}
|
|
142
155
|
const apiResponse = await callApi(request, platform);
|
|
143
156
|
if (apiResponse) return apiResponse;
|
|
157
|
+
const pageCache = cachePages && platform && source === "workers" && isCacheablePageRequest(request) ? defaultPageCache() : void 0;
|
|
158
|
+
const cacheKey = pageCache ? new Request(request.url) : void 0;
|
|
159
|
+
if (pageCache && cacheKey) {
|
|
160
|
+
const cached = await pageCache.match(cacheKey);
|
|
161
|
+
if (cached) return cached;
|
|
162
|
+
}
|
|
144
163
|
const context = platform ? createContext(platform, source) : void 0;
|
|
145
|
-
|
|
164
|
+
const response = await render(strip ? stripProxyHeaders(request) : request, context);
|
|
165
|
+
if (!response) return new Response("Page not found.", { status: 404 });
|
|
166
|
+
if (pageCache && cacheKey && platform && isCacheablePageResponse(response)) platform.ctx.waitUntil(pageCache.put(cacheKey, response.clone()).catch((error) => {
|
|
167
|
+
console.error(`ngx-cloudflare: could not cache ${request.url}`, error);
|
|
168
|
+
}));
|
|
169
|
+
return response;
|
|
146
170
|
};
|
|
147
171
|
}
|
|
148
172
|
function loadPlatformProxy(options) {
|
|
@@ -169,6 +193,6 @@ function createCloudflareRequestHandler(options = {}) {
|
|
|
169
193
|
createRequestHandler(handler);
|
|
170
194
|
return handler;
|
|
171
195
|
}
|
|
172
|
-
export {
|
|
196
|
+
export { createCloudflareFetch, createCloudflareRequestHandler, hostsFromEnv, isCacheablePageRequest, isCacheablePageResponse, isHostAllowed, isInProcessRequest, resolveAllowedHosts, stripProxyHeaders, ɵCLOUDFLARE_CONTEXT_MARKER, ɵcallInProcess, ɵisCloudflareContext, ɵtoApiHandler };
|
|
173
197
|
|
|
174
198
|
//# sourceMappingURL=ngx-cloudflare-server.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngx-cloudflare-server.mjs","names":[],"sources":["../../../projects/ngx-cloudflare/server/src/cloudflare-fetch.ts","../../../projects/ngx-cloudflare/server/src/cloudflare-request-handler.ts"],"sourcesContent":["declare global {\n namespace Cloudflare {\n interface Env {}\n }\n}\n\nexport const CLOUDFLARE_CONTEXT_MARKER: unique symbol = Symbol.for(\n 'ngx-cloudflare.context',\n) as never;\n\nexport const DEFAULT_ALLOWED_HOSTS: readonly string[] = ['localhost', '127.0.0.1', '*.workers.dev'];\n\nexport const DEFAULT_API_PREFIX = '/api/';\n\nconst MAX_LOGGED_REFUSALS = 100;\n\nexport interface CloudflareExecutionContext {\n waitUntil(promise: Promise<unknown>): void;\n passThroughOnException(): void;\n}\n\nexport type CloudflareRequestCf = Record<string, unknown>;\n\nexport type CloudflareContextSource = 'workers' | 'platform-proxy';\n\nexport interface CloudflarePlatform<Env = Cloudflare.Env, Cf = CloudflareRequestCf> {\n readonly env: Env;\n readonly ctx: CloudflareExecutionContext;\n readonly cf: Cf | undefined;\n}\n\nexport interface CloudflareContext<\n Env = Cloudflare.Env,\n Cf = CloudflareRequestCf,\n> extends CloudflarePlatform<Env, Cf> {\n readonly source: CloudflareContextSource;\n readonly dispatch: (request: Request) => Promise<Response | null>;\n readonly [CLOUDFLARE_CONTEXT_MARKER]: true;\n}\n\nexport type ApiResult = Response | null | undefined;\n\nexport type ApiHandler<Env = Cloudflare.Env> = (\n request: Request,\n env: Env,\n ctx: CloudflareExecutionContext,\n) => ApiResult | Promise<ApiResult>;\n\nexport type ApiOption<Env = Cloudflare.Env> = ApiHandler<Env> | { fetch: ApiHandler<Env> };\n\nexport interface CloudflareHandlerOptions<Env = Cloudflare.Env> {\n allowedHosts?: readonly string[];\n api?: ApiOption<Env>;\n apiPrefix?: string | false;\n stripProxyHeaders?: boolean;\n}\n\nexport type RenderFunction = (\n request: Request,\n context: CloudflareContext<any, any> | undefined,\n) => Promise<Response | null>;\n\nexport type DevPlatformLoader = () => Promise<CloudflarePlatform<any, any> | undefined> | undefined;\n\nexport type CloudflareFetchHandler<Env = Cloudflare.Env> = (\n request: Request,\n env?: Env,\n ctx?: CloudflareExecutionContext,\n) => Promise<Response>;\n\nconst inProcessRequests = new WeakSet<Request>();\n\nexport function isInProcessRequest(request: Request): boolean {\n return inProcessRequests.has(request);\n}\n\nexport function isCloudflareContext(value: unknown): value is CloudflareContext<any, any> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[CLOUDFLARE_CONTEXT_MARKER] === true\n );\n}\n\nexport const ALLOWED_HOSTS_VAR = 'ALLOWED_HOSTS';\n\nexport function hostsFromEnv(env: unknown): string[] {\n const value = (env as Record<string, unknown> | undefined)?.[ALLOWED_HOSTS_VAR];\n if (typeof value !== 'string') {\n return [];\n }\n return value\n .split(/[\\s,]+/)\n .map((host) => host.trim())\n .filter(Boolean);\n}\n\nexport function resolveAllowedHosts(extra: readonly string[] = []): string[] {\n const hosts: string[] = [];\n for (const host of [...DEFAULT_ALLOWED_HOSTS, ...extra]) {\n const normalized = host.trim().toLowerCase();\n if (normalized && !hosts.includes(normalized)) {\n hosts.push(normalized);\n }\n }\n return hosts;\n}\n\nexport function isHostAllowed(hostname: string, allowedHosts: readonly string[]): boolean {\n const host = hostname.toLowerCase().replace(/\\.$/, '');\n for (const pattern of allowedHosts) {\n if (pattern === '*') {\n return true;\n }\n if (pattern.startsWith('*.')) {\n const suffix = pattern.slice(1);\n if (host.endsWith(suffix) && host.length > suffix.length) {\n return true;\n }\n } else if (host === pattern) {\n return true;\n }\n }\n return false;\n}\n\nexport function hostRefusal(hostname: string): Response {\n return new Response(\n `Host \"${hostname}\" is not allowed by ngx-cloudflare. ` +\n `Add it to ALLOWED_HOSTS in the vars of wrangler.jsonc, or to allowedHosts in src/server.ts. ` +\n `localhost, 127.0.0.1 and *.workers.dev are allowed by default.`,\n { status: 400, headers: { 'content-type': 'text/plain; charset=utf-8' } },\n );\n}\n\nexport function isProxyHeader(name: string): boolean {\n const lower = name.toLowerCase();\n return lower === 'forwarded' || lower.startsWith('x-forwarded-');\n}\n\nexport function stripProxyHeaders(request: Request): Request {\n const names: string[] = [];\n request.headers.forEach((_value, name) => {\n if (isProxyHeader(name)) {\n names.push(name);\n }\n });\n if (names.length === 0) {\n return request;\n }\n const headers = new Headers(request.headers);\n for (const name of names) {\n headers.delete(name);\n }\n return new Request(request, { headers });\n}\n\nexport function toApiHandler<Env>(api: ApiOption<Env> | undefined): ApiHandler<Env> | undefined {\n if (!api) {\n return undefined;\n }\n if (typeof api === 'function') {\n return api;\n }\n return (request, env, ctx) => api.fetch(request, env, ctx);\n}\n\nfunction matchesPrefix(request: Request, prefix: string | false): boolean {\n if (prefix === false) {\n return true;\n }\n const { pathname } = new URL(request.url);\n const bare = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;\n return pathname === bare || pathname.startsWith(bare + '/');\n}\n\nexport async function callInProcess<Env>(\n handler: ApiHandler<Env>,\n request: Request,\n env: Env,\n ctx: CloudflareExecutionContext,\n): Promise<Response | null> {\n inProcessRequests.add(request);\n return (await handler(request, env, ctx)) ?? null;\n}\n\nconst NOOP_CONTEXT: CloudflareExecutionContext = {\n waitUntil() {},\n passThroughOnException() {},\n};\n\nexport function createCloudflareFetch<Env = Cloudflare.Env>(\n render: RenderFunction,\n options: CloudflareHandlerOptions<Env> = {},\n loadDevPlatform?: DevPlatformLoader,\n): CloudflareFetchHandler<Env> {\n const configuredHosts = options.allowedHosts ?? [];\n const hostsByEnv = new WeakMap<object, string[]>();\n const allowedHostsFor = (env: unknown): string[] => {\n if (typeof env !== 'object' || env === null) {\n return resolveAllowedHosts(configuredHosts);\n }\n let hosts = hostsByEnv.get(env);\n if (!hosts) {\n hosts = resolveAllowedHosts([...configuredHosts, ...hostsFromEnv(env)]);\n hostsByEnv.set(env, hosts);\n }\n return hosts;\n };\n const api = toApiHandler(options.api);\n const apiPrefix = options.apiPrefix ?? DEFAULT_API_PREFIX;\n const strip = options.stripProxyHeaders ?? true;\n const loggedRefusals = new Set<string>();\n\n async function callApi(\n request: Request,\n platform: CloudflarePlatform<Env> | undefined,\n ): Promise<Response | null> {\n if (!api || !matchesPrefix(request, apiPrefix)) {\n return null;\n }\n const result = await api(request, platform?.env as Env, platform?.ctx ?? NOOP_CONTEXT);\n return result ?? null;\n }\n\n async function serveAsset(\n request: Request,\n platform: CloudflarePlatform<Env> | undefined,\n ): Promise<Response | null> {\n const assets = (\n platform?.env as { ASSETS?: { fetch(request: Request): Promise<Response> } } | undefined\n )?.ASSETS;\n if (\n !assets ||\n typeof assets.fetch !== 'function' ||\n (request.method !== 'GET' && request.method !== 'HEAD')\n ) {\n return null;\n }\n const response = await assets.fetch(request);\n return response.status === 404 ? null : response;\n }\n\n function createContext(\n platform: CloudflarePlatform<Env>,\n source: CloudflareContextSource,\n ): CloudflareContext<Env> {\n return {\n env: platform.env,\n ctx: platform.ctx,\n cf: platform.cf,\n source,\n dispatch: async (request) => {\n inProcessRequests.add(request);\n return (await callApi(request, platform)) ?? (await serveAsset(request, platform));\n },\n [CLOUDFLARE_CONTEXT_MARKER]: true,\n };\n }\n\n return async (request, env, ctx) => {\n let platform: CloudflarePlatform<Env> | undefined;\n let source: CloudflareContextSource = 'workers';\n if (env !== undefined) {\n platform = {\n env,\n ctx: ctx ?? NOOP_CONTEXT,\n cf: (request as Request & { cf?: CloudflareRequestCf }).cf,\n };\n } else if (loadDevPlatform) {\n platform = await loadDevPlatform();\n source = 'platform-proxy';\n }\n\n const { hostname } = new URL(request.url);\n const allowedHosts = allowedHostsFor(platform?.env);\n if (!isHostAllowed(hostname, allowedHosts)) {\n if (!loggedRefusals.has(hostname)) {\n if (loggedRefusals.size >= MAX_LOGGED_REFUSALS) {\n loggedRefusals.clear();\n }\n loggedRefusals.add(hostname);\n console.error(\n `ngx-cloudflare: refused host \"${hostname}\". Allowed hosts: ${allowedHosts.join(', ')}`,\n );\n }\n return hostRefusal(hostname);\n }\n\n const apiResponse = await callApi(request, platform);\n if (apiResponse) {\n return apiResponse;\n }\n\n const context = platform ? createContext(platform, source) : undefined;\n const response = await render(strip ? stripProxyHeaders(request) : request, context);\n return response ?? new Response('Page not found.', { status: 404 });\n };\n}\n","import { AngularAppEngine, createRequestHandler } from '@angular/ssr';\nimport {\n CloudflareFetchHandler,\n CloudflareHandlerOptions,\n CloudflarePlatform,\n createCloudflareFetch,\n hostsFromEnv,\n resolveAllowedHosts,\n} from './cloudflare-fetch';\n\ndeclare const ngDevMode: boolean | undefined;\n\nexport interface DevPlatformOptions {\n configPath?: string;\n environment?: string;\n persist?: boolean | { path: string };\n}\n\nexport interface CloudflareRequestHandlerOptions<\n Env = Cloudflare.Env,\n> extends CloudflareHandlerOptions<Env> {\n devPlatform?: DevPlatformOptions | false;\n}\n\ninterface ProxyCache {\n __ngxCloudflarePlatformProxy?: Promise<CloudflarePlatform<any, any> | undefined>;\n}\n\nfunction loadPlatformProxy(\n options: DevPlatformOptions,\n): Promise<CloudflarePlatform<any, any> | undefined> {\n const cache = globalThis as ProxyCache;\n cache.__ngxCloudflarePlatformProxy ??= import('wrangler')\n .then((wrangler) => wrangler.getPlatformProxy(options))\n .then((proxy) => ({ env: proxy.env, ctx: proxy.ctx, cf: proxy.cf }))\n .catch((error: unknown) => {\n cache.__ngxCloudflarePlatformProxy = undefined;\n console.error(\n \"ngx-cloudflare: could not start wrangler's platform proxy, so bindings are unavailable in ng serve. \" +\n 'Install wrangler as a dev dependency and check wrangler.jsonc. The next request tries again.',\n error,\n );\n return undefined;\n });\n return cache.__ngxCloudflarePlatformProxy;\n}\n\nexport function createCloudflareRequestHandler<Env = Cloudflare.Env>(\n options: CloudflareRequestHandlerOptions<Env> = {},\n): CloudflareFetchHandler<Env> {\n let engine: AngularAppEngine | undefined;\n const devPlatform = options.devPlatform;\n\n const handler = createCloudflareFetch<Env>(\n (request, context) => {\n engine ??= new AngularAppEngine({\n allowedHosts: resolveAllowedHosts([\n ...(options.allowedHosts ?? []),\n ...hostsFromEnv(context?.env),\n ]),\n });\n return engine.handle(request, context);\n },\n options,\n () => {\n if ((typeof ngDevMode === 'undefined' || ngDevMode) && devPlatform !== false) {\n return loadPlatformProxy(devPlatform ?? {});\n }\n return undefined;\n },\n );\n\n createRequestHandler(handler as (request: Request) => Promise<Response>);\n return handler;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngx-cloudflare-server.mjs","names":[],"sources":["../../../projects/ngx-cloudflare/server/src/cloudflare-fetch.ts","../../../projects/ngx-cloudflare/server/src/cloudflare-request-handler.ts"],"sourcesContent":["declare global {\n namespace Cloudflare {\n interface Env {}\n }\n}\n\nexport const ɵCLOUDFLARE_CONTEXT_MARKER: unique symbol = Symbol.for(\n 'ngx-cloudflare.context',\n) as never;\n\nconst DEFAULT_ALLOWED_HOSTS: readonly string[] = ['localhost', '127.0.0.1', '*.workers.dev'];\n\nconst DEFAULT_API_PREFIX = '/api/';\n\nconst MAX_LOGGED_REFUSALS = 100;\n\nexport interface CloudflareExecutionContext {\n waitUntil(promise: Promise<unknown>): void;\n passThroughOnException(): void;\n}\n\nexport type CloudflareRequestCf = Record<string, unknown>;\n\nexport type CloudflareContextSource = 'workers' | 'platform-proxy';\n\nexport interface CloudflarePlatform<Env = Cloudflare.Env, Cf = CloudflareRequestCf> {\n readonly env: Env;\n readonly ctx: CloudflareExecutionContext;\n readonly cf: Cf | undefined;\n}\n\nexport interface CloudflareContext<\n Env = Cloudflare.Env,\n Cf = CloudflareRequestCf,\n> extends CloudflarePlatform<Env, Cf> {\n readonly source: CloudflareContextSource;\n readonly dispatch: (request: Request) => Promise<Response | null>;\n readonly [ɵCLOUDFLARE_CONTEXT_MARKER]: true;\n}\n\nexport type ApiResult = Response | null | undefined;\n\nexport type ApiHandler<Env = Cloudflare.Env> = (\n request: Request,\n env: Env,\n ctx: CloudflareExecutionContext,\n) => ApiResult | Promise<ApiResult>;\n\nexport type ApiOption<Env = Cloudflare.Env> = ApiHandler<Env> | { fetch: ApiHandler<Env> };\n\nexport interface CloudflareHandlerOptions<Env = Cloudflare.Env> {\n allowedHosts?: readonly string[];\n api?: ApiOption<Env>;\n apiPrefix?: string | false;\n stripProxyHeaders?: boolean;\n cachePages?: boolean;\n}\n\nexport type RenderFunction = (\n request: Request,\n context: CloudflareContext<any, any> | undefined,\n) => Promise<Response | null>;\n\nexport type DevPlatformLoader = () => Promise<CloudflarePlatform<any, any> | undefined> | undefined;\n\nexport type CloudflareFetchHandler<Env = Cloudflare.Env> = (\n request: Request,\n env?: Env,\n ctx?: CloudflareExecutionContext,\n) => Promise<Response>;\n\nconst inProcessRequests = new WeakSet<Request>();\n\nexport function isInProcessRequest(request: Request): boolean {\n return inProcessRequests.has(request);\n}\n\nexport function ɵisCloudflareContext(value: unknown): value is CloudflareContext<any, any> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[ɵCLOUDFLARE_CONTEXT_MARKER] === true\n );\n}\n\nconst ALLOWED_HOSTS_VAR = 'ALLOWED_HOSTS';\n\nexport function hostsFromEnv(env: unknown): string[] {\n const value = (env as Record<string, unknown> | undefined)?.[ALLOWED_HOSTS_VAR];\n if (typeof value !== 'string') {\n return [];\n }\n return value\n .split(/[\\s,]+/)\n .map((host) => host.trim())\n .filter(Boolean);\n}\n\nexport function resolveAllowedHosts(extra: readonly string[] = []): string[] {\n const hosts: string[] = [];\n for (const host of [...DEFAULT_ALLOWED_HOSTS, ...extra]) {\n const normalized = host.trim().toLowerCase();\n if (normalized && !hosts.includes(normalized)) {\n hosts.push(normalized);\n }\n }\n return hosts;\n}\n\nexport function isHostAllowed(hostname: string, allowedHosts: readonly string[]): boolean {\n const host = hostname.toLowerCase().replace(/\\.$/, '');\n for (const pattern of allowedHosts) {\n if (pattern === '*') {\n return true;\n }\n if (pattern.startsWith('*.')) {\n const suffix = pattern.slice(1);\n if (host.endsWith(suffix) && host.length > suffix.length) {\n return true;\n }\n } else if (host === pattern) {\n return true;\n }\n }\n return false;\n}\n\nfunction hostRefusal(hostname: string): Response {\n return new Response(\n `Host \"${hostname}\" is not allowed by ngx-cloudflare. ` +\n `Add it to ALLOWED_HOSTS in the vars of wrangler.jsonc, or to allowedHosts in src/server.ts. ` +\n `localhost, 127.0.0.1 and *.workers.dev are allowed by default.`,\n { status: 400, headers: { 'content-type': 'text/plain; charset=utf-8' } },\n );\n}\n\nfunction isProxyHeader(name: string): boolean {\n const lower = name.toLowerCase();\n return lower === 'forwarded' || lower.startsWith('x-forwarded-');\n}\n\nexport function stripProxyHeaders(request: Request): Request {\n const names: string[] = [];\n request.headers.forEach((_value, name) => {\n if (isProxyHeader(name)) {\n names.push(name);\n }\n });\n if (names.length === 0) {\n return request;\n }\n const headers = new Headers(request.headers);\n for (const name of names) {\n headers.delete(name);\n }\n return new Request(request, { headers });\n}\n\nexport interface PageCache {\n match(request: Request): Promise<Response | undefined>;\n put(request: Request, response: Response): Promise<void>;\n}\n\nfunction defaultPageCache(): PageCache | undefined {\n return (globalThis as { caches?: { default?: PageCache } }).caches?.default;\n}\n\nexport function isCacheablePageRequest(request: Request): boolean {\n return (\n request.method === 'GET' &&\n !request.headers.has('authorization') &&\n !request.headers.has('cookie')\n );\n}\n\nexport function isCacheablePageResponse(response: Response): boolean {\n if (response.status !== 200 || response.headers.has('set-cookie')) {\n return false;\n }\n const cacheControl = response.headers.get('cache-control')?.toLowerCase() ?? '';\n if (/(?:^|,)\\s*(?:private|no-store|no-cache)\\b/.test(cacheControl)) {\n return false;\n }\n return /(?:^|,)\\s*(?:s-maxage|max-age)\\s*=\\s*0*[1-9]/.test(cacheControl);\n}\n\nexport function ɵtoApiHandler<Env>(api: ApiOption<Env> | undefined): ApiHandler<Env> | undefined {\n if (!api) {\n return undefined;\n }\n if (typeof api === 'function') {\n return api;\n }\n return (request, env, ctx) => api.fetch(request, env, ctx);\n}\n\nfunction matchesPrefix(request: Request, prefix: string | false): boolean {\n if (prefix === false) {\n return true;\n }\n const { pathname } = new URL(request.url);\n const bare = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;\n return pathname === bare || pathname.startsWith(bare + '/');\n}\n\nexport async function ɵcallInProcess<Env>(\n handler: ApiHandler<Env>,\n request: Request,\n env: Env,\n ctx: CloudflareExecutionContext,\n): Promise<Response | null> {\n inProcessRequests.add(request);\n return (await handler(request, env, ctx)) ?? null;\n}\n\nconst NOOP_CONTEXT: CloudflareExecutionContext = {\n waitUntil() {},\n passThroughOnException() {},\n};\n\nexport function createCloudflareFetch<Env = Cloudflare.Env>(\n render: RenderFunction,\n options: CloudflareHandlerOptions<Env> = {},\n loadDevPlatform?: DevPlatformLoader,\n): CloudflareFetchHandler<Env> {\n const configuredHosts = options.allowedHosts ?? [];\n const hostsByEnv = new WeakMap<object, string[]>();\n const allowedHostsFor = (env: unknown): string[] => {\n if (typeof env !== 'object' || env === null) {\n return resolveAllowedHosts(configuredHosts);\n }\n let hosts = hostsByEnv.get(env);\n if (!hosts) {\n hosts = resolveAllowedHosts([...configuredHosts, ...hostsFromEnv(env)]);\n hostsByEnv.set(env, hosts);\n }\n return hosts;\n };\n const api = ɵtoApiHandler(options.api);\n const apiPrefix = options.apiPrefix ?? DEFAULT_API_PREFIX;\n const strip = options.stripProxyHeaders ?? true;\n const cachePages = options.cachePages ?? false;\n const loggedRefusals = new Set<string>();\n\n async function callApi(\n request: Request,\n platform: CloudflarePlatform<Env> | undefined,\n ): Promise<Response | null> {\n if (!api || !matchesPrefix(request, apiPrefix)) {\n return null;\n }\n const result = await api(request, platform?.env as Env, platform?.ctx ?? NOOP_CONTEXT);\n return result ?? null;\n }\n\n async function serveAsset(\n request: Request,\n platform: CloudflarePlatform<Env> | undefined,\n ): Promise<Response | null> {\n const assets = (\n platform?.env as { ASSETS?: { fetch(request: Request): Promise<Response> } } | undefined\n )?.ASSETS;\n if (\n !assets ||\n typeof assets.fetch !== 'function' ||\n (request.method !== 'GET' && request.method !== 'HEAD')\n ) {\n return null;\n }\n const response = await assets.fetch(request);\n return response.status === 404 ? null : response;\n }\n\n function createContext(\n platform: CloudflarePlatform<Env>,\n source: CloudflareContextSource,\n ): CloudflareContext<Env> {\n return {\n env: platform.env,\n ctx: platform.ctx,\n cf: platform.cf,\n source,\n dispatch: async (request) => {\n inProcessRequests.add(request);\n return (await callApi(request, platform)) ?? (await serveAsset(request, platform));\n },\n [ɵCLOUDFLARE_CONTEXT_MARKER]: true,\n };\n }\n\n return async (request, env, ctx) => {\n let platform: CloudflarePlatform<Env> | undefined;\n let source: CloudflareContextSource = 'workers';\n if (env !== undefined) {\n platform = {\n env,\n ctx: ctx ?? NOOP_CONTEXT,\n cf: (request as Request & { cf?: CloudflareRequestCf }).cf,\n };\n } else if (loadDevPlatform) {\n platform = await loadDevPlatform();\n source = 'platform-proxy';\n }\n\n const { hostname } = new URL(request.url);\n const allowedHosts = allowedHostsFor(platform?.env);\n if (!isHostAllowed(hostname, allowedHosts)) {\n if (!loggedRefusals.has(hostname)) {\n if (loggedRefusals.size >= MAX_LOGGED_REFUSALS) {\n loggedRefusals.clear();\n }\n loggedRefusals.add(hostname);\n console.error(\n `ngx-cloudflare: refused host \"${hostname}\". Allowed hosts: ${allowedHosts.join(', ')}`,\n );\n }\n return hostRefusal(hostname);\n }\n\n const apiResponse = await callApi(request, platform);\n if (apiResponse) {\n return apiResponse;\n }\n\n const pageCache =\n cachePages && platform && source === 'workers' && isCacheablePageRequest(request)\n ? defaultPageCache()\n : undefined;\n const cacheKey = pageCache ? new Request(request.url) : undefined;\n if (pageCache && cacheKey) {\n const cached = await pageCache.match(cacheKey);\n if (cached) {\n return cached;\n }\n }\n\n const context = platform ? createContext(platform, source) : undefined;\n const response = await render(strip ? stripProxyHeaders(request) : request, context);\n if (!response) {\n return new Response('Page not found.', { status: 404 });\n }\n if (pageCache && cacheKey && platform && isCacheablePageResponse(response)) {\n platform.ctx.waitUntil(\n pageCache.put(cacheKey, response.clone()).catch((error: unknown) => {\n console.error(`ngx-cloudflare: could not cache ${request.url}`, error);\n }),\n );\n }\n return response;\n };\n}\n","import { AngularAppEngine, createRequestHandler } from '@angular/ssr';\nimport {\n CloudflareFetchHandler,\n CloudflareHandlerOptions,\n CloudflarePlatform,\n createCloudflareFetch,\n hostsFromEnv,\n resolveAllowedHosts,\n} from './cloudflare-fetch';\n\ndeclare const ngDevMode: boolean | undefined;\n\nexport interface DevPlatformOptions {\n configPath?: string;\n environment?: string;\n persist?: boolean | { path: string };\n}\n\nexport interface CloudflareRequestHandlerOptions<\n Env = Cloudflare.Env,\n> extends CloudflareHandlerOptions<Env> {\n devPlatform?: DevPlatformOptions | false;\n}\n\ninterface ProxyCache {\n __ngxCloudflarePlatformProxy?: Promise<CloudflarePlatform<any, any> | undefined>;\n}\n\nfunction loadPlatformProxy(\n options: DevPlatformOptions,\n): Promise<CloudflarePlatform<any, any> | undefined> {\n const cache = globalThis as ProxyCache;\n cache.__ngxCloudflarePlatformProxy ??= import('wrangler')\n .then((wrangler) => wrangler.getPlatformProxy(options))\n .then((proxy) => ({ env: proxy.env, ctx: proxy.ctx, cf: proxy.cf }))\n .catch((error: unknown) => {\n cache.__ngxCloudflarePlatformProxy = undefined;\n console.error(\n \"ngx-cloudflare: could not start wrangler's platform proxy, so bindings are unavailable in ng serve. \" +\n 'Install wrangler as a dev dependency and check wrangler.jsonc. The next request tries again.',\n error,\n );\n return undefined;\n });\n return cache.__ngxCloudflarePlatformProxy;\n}\n\nexport function createCloudflareRequestHandler<Env = Cloudflare.Env>(\n options: CloudflareRequestHandlerOptions<Env> = {},\n): CloudflareFetchHandler<Env> {\n let engine: AngularAppEngine | undefined;\n const devPlatform = options.devPlatform;\n\n const handler = createCloudflareFetch<Env>(\n (request, context) => {\n engine ??= new AngularAppEngine({\n allowedHosts: resolveAllowedHosts([\n ...(options.allowedHosts ?? []),\n ...hostsFromEnv(context?.env),\n ]),\n });\n return engine.handle(request, context);\n },\n options,\n () => {\n if ((typeof ngDevMode === 'undefined' || ngDevMode) && devPlatform !== false) {\n return loadPlatformProxy(devPlatform ?? {});\n }\n return undefined;\n },\n );\n\n createRequestHandler(handler as (request: Request) => Promise<Response>);\n return handler;\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { REQUEST, REQUEST_CONTEXT, makeEnvironmentProviders } from "@angular/core";
|
|
2
|
-
import { CLOUDFLARE_CONTEXT_MARKER,
|
|
3
|
-
function
|
|
2
|
+
import { ɵCLOUDFLARE_CONTEXT_MARKER, ɵcallInProcess, ɵtoApiHandler } from "ngx-cloudflare/server";
|
|
3
|
+
function createCloudflareTestExecutionContext(overrides = {}) {
|
|
4
4
|
const waitUntilPromises = [];
|
|
5
5
|
return {
|
|
6
6
|
waitUntilPromises,
|
|
@@ -13,15 +13,15 @@ function createTestExecutionContext(overrides = {}) {
|
|
|
13
13
|
}
|
|
14
14
|
function createCloudflareTestContext(options = {}) {
|
|
15
15
|
const env = options.env ?? {};
|
|
16
|
-
const ctx =
|
|
17
|
-
const handler =
|
|
16
|
+
const ctx = createCloudflareTestExecutionContext(options.ctx);
|
|
17
|
+
const handler = ɵtoApiHandler(options.api);
|
|
18
18
|
return {
|
|
19
19
|
env,
|
|
20
20
|
ctx,
|
|
21
21
|
cf: options.cf,
|
|
22
22
|
source: options.source ?? "workers",
|
|
23
|
-
dispatch: async (request) => handler ?
|
|
24
|
-
[CLOUDFLARE_CONTEXT_MARKER]: true
|
|
23
|
+
dispatch: async (request) => handler ? ɵcallInProcess(handler, request, env, ctx) : null,
|
|
24
|
+
[ɵCLOUDFLARE_CONTEXT_MARKER]: true
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
function provideCloudflareTesting(options = {}) {
|
|
@@ -33,6 +33,6 @@ function provideCloudflareTesting(options = {}) {
|
|
|
33
33
|
useValue: new Request(options.url ?? "http://localhost/")
|
|
34
34
|
}]);
|
|
35
35
|
}
|
|
36
|
-
export { createCloudflareTestContext,
|
|
36
|
+
export { createCloudflareTestContext, createCloudflareTestExecutionContext, provideCloudflareTesting };
|
|
37
37
|
|
|
38
38
|
//# sourceMappingURL=ngx-cloudflare-testing.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngx-cloudflare-testing.mjs","names":[],"sources":["../../../projects/ngx-cloudflare/testing/src/provide-cloudflare-testing.ts"],"sourcesContent":["import {\n EnvironmentProviders,\n makeEnvironmentProviders,\n REQUEST,\n REQUEST_CONTEXT,\n} from '@angular/core';\nimport {\n ApiOption,\n
|
|
1
|
+
{"version":3,"file":"ngx-cloudflare-testing.mjs","names":[],"sources":["../../../projects/ngx-cloudflare/testing/src/provide-cloudflare-testing.ts"],"sourcesContent":["import {\n EnvironmentProviders,\n makeEnvironmentProviders,\n REQUEST,\n REQUEST_CONTEXT,\n} from '@angular/core';\nimport {\n ApiOption,\n ɵcallInProcess,\n ɵCLOUDFLARE_CONTEXT_MARKER,\n type CloudflareContext,\n type CloudflareContextSource,\n type CloudflareExecutionContext,\n type CloudflareRequestCf,\n ɵtoApiHandler,\n} from 'ngx-cloudflare/server';\n\nexport interface CloudflareTestingOptions<Env = Cloudflare.Env, Cf = CloudflareRequestCf> {\n env?: Partial<Env>;\n cf?: Cf;\n ctx?: Partial<CloudflareExecutionContext>;\n url?: string;\n api?: ApiOption<Env>;\n source?: CloudflareContextSource;\n}\n\nexport interface TestExecutionContext extends CloudflareExecutionContext {\n readonly waitUntilPromises: Promise<unknown>[];\n}\n\nexport function createCloudflareTestExecutionContext(\n overrides: Partial<CloudflareExecutionContext> = {},\n): TestExecutionContext {\n const waitUntilPromises: Promise<unknown>[] = [];\n return {\n waitUntilPromises,\n waitUntil: (promise) => {\n waitUntilPromises.push(promise);\n },\n passThroughOnException: () => {},\n ...overrides,\n };\n}\n\nexport function createCloudflareTestContext<Env = Cloudflare.Env, Cf = CloudflareRequestCf>(\n options: CloudflareTestingOptions<Env, Cf> = {},\n): CloudflareContext<Env, Cf> {\n const env = (options.env ?? {}) as Env;\n const ctx = createCloudflareTestExecutionContext(options.ctx);\n const handler = ɵtoApiHandler(options.api);\n return {\n env,\n ctx,\n cf: options.cf,\n source: options.source ?? 'workers',\n dispatch: async (request) => (handler ? ɵcallInProcess(handler, request, env, ctx) : null),\n [ɵCLOUDFLARE_CONTEXT_MARKER]: true,\n };\n}\n\nexport function provideCloudflareTesting<Env = Cloudflare.Env, Cf = CloudflareRequestCf>(\n options: CloudflareTestingOptions<Env, Cf> = {},\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: REQUEST_CONTEXT, useValue: createCloudflareTestContext(options) },\n { provide: REQUEST, useValue: new Request(options.url ?? 'http://localhost/') },\n ]);\n}\n"],"mappings":";;AA8BA,SAAgB,qCACd,YAAiD,CAAA,GAAE;CAEnD,MAAM,oBAAwC,CAAA;CAC9C,OAAO;EACL;EACA,YAAY,YAAW;GACrB,kBAAkB,KAAK,OAAO;EAChC;EACA,8BAA6B,CAAE;EAC/B,GAAG;CACJ;AACH;AAEA,SAAgB,4BACd,UAA6C,CAAA,GAAE;CAE/C,MAAM,MAAO,QAAQ,OAAO,CAAA;CAC5B,MAAM,MAAM,qCAAqC,QAAQ,GAAG;CAC5D,MAAM,UAAU,cAAc,QAAQ,GAAG;CACzC,OAAO;EACL;EACA;EACA,IAAI,QAAQ;EACZ,QAAQ,QAAQ,UAAU;EAC1B,UAAU,OAAO,YAAa,UAAU,eAAe,SAAS,SAAS,KAAK,GAAG,IAAI;GACpF,6BAA6B;CAC/B;AACH;AAEA,SAAgB,yBACd,UAA6C,CAAA,GAAE;CAE/C,OAAO,yBAAyB,CAC9B;EAAE,SAAS;EAAiB,UAAU,4BAA4B,OAAO;CAAC,GAC1E;EAAE,SAAS;EAAS,UAAU,IAAI,QAAQ,QAAQ,OAAO,mBAAmB;CAAC,CAC9E,CAAA;AACH"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
2
|
import { Injectable, REQUEST, REQUEST_CONTEXT, assertInInjectionContext, inject, makeEnvironmentProviders } from "@angular/core";
|
|
3
|
-
import {
|
|
3
|
+
import { ɵisCloudflareContext } from "ngx-cloudflare/server";
|
|
4
4
|
import { FetchBackend, HttpBackend, HttpErrorResponse, HttpEventType, HttpHeaders, HttpResponse } from "@angular/common/http";
|
|
5
5
|
import { Observable } from "rxjs";
|
|
6
6
|
const MISSING_CONTEXT = "ngx-cloudflare: no Cloudflare request context. Bindings exist only while a request is rendered on the server (on Workers or in ng serve). They are null in the browser, during prerendering and at build time. Check that src/server.ts uses createCloudflareRequestHandler(), or call this without { required: true } and handle null.";
|
|
7
7
|
function readContext(fn) {
|
|
8
8
|
assertInInjectionContext(fn);
|
|
9
9
|
const value = inject(REQUEST_CONTEXT, { optional: true });
|
|
10
|
-
return
|
|
10
|
+
return ɵisCloudflareContext(value) ? value : null;
|
|
11
11
|
}
|
|
12
12
|
function orThrow(value, options) {
|
|
13
13
|
if (value === null && options?.required) throw new Error(MISSING_CONTEXT);
|
|
@@ -69,12 +69,12 @@ async function readBody(req, res) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
var
|
|
72
|
+
var ɵCloudflareFetchBackend = class ɵCloudflareFetchBackend extends FetchBackend {
|
|
73
73
|
cloudflare = inject(REQUEST_CONTEXT, { optional: true });
|
|
74
74
|
incoming = inject(REQUEST, { optional: true });
|
|
75
75
|
handle(req) {
|
|
76
76
|
const context = this.cloudflare;
|
|
77
|
-
if (!this.incoming ||
|
|
77
|
+
if (!this.incoming || !ɵisCloudflareContext(context)) return super.handle(req);
|
|
78
78
|
const base = new URL(this.incoming.url);
|
|
79
79
|
const url = new URL(req.urlWithParams, base);
|
|
80
80
|
if (url.origin !== base.origin) return super.handle(req);
|
|
@@ -138,7 +138,7 @@ var CloudflareFetchBackend = class CloudflareFetchBackend extends FetchBackend {
|
|
|
138
138
|
minVersion: "12.0.0",
|
|
139
139
|
version: "22.2.5",
|
|
140
140
|
ngImport: i0,
|
|
141
|
-
type:
|
|
141
|
+
type: ɵCloudflareFetchBackend,
|
|
142
142
|
deps: null,
|
|
143
143
|
target: i0.ɵɵFactoryTarget.Injectable
|
|
144
144
|
});
|
|
@@ -146,30 +146,30 @@ var CloudflareFetchBackend = class CloudflareFetchBackend extends FetchBackend {
|
|
|
146
146
|
minVersion: "12.0.0",
|
|
147
147
|
version: "22.2.5",
|
|
148
148
|
ngImport: i0,
|
|
149
|
-
type:
|
|
149
|
+
type: ɵCloudflareFetchBackend
|
|
150
150
|
});
|
|
151
151
|
};
|
|
152
152
|
i0.ɵɵngDeclareClassMetadata({
|
|
153
153
|
minVersion: "12.0.0",
|
|
154
154
|
version: "22.2.5",
|
|
155
155
|
ngImport: i0,
|
|
156
|
-
type:
|
|
156
|
+
type: ɵCloudflareFetchBackend,
|
|
157
157
|
decorators: [{ type: Injectable }]
|
|
158
158
|
});
|
|
159
159
|
function provideCloudflare(options = {}) {
|
|
160
160
|
if (options.inProcessRequests === false) return makeEnvironmentProviders([]);
|
|
161
161
|
return makeEnvironmentProviders([
|
|
162
|
-
|
|
162
|
+
ɵCloudflareFetchBackend,
|
|
163
163
|
{
|
|
164
164
|
provide: FetchBackend,
|
|
165
|
-
useExisting:
|
|
165
|
+
useExisting: ɵCloudflareFetchBackend
|
|
166
166
|
},
|
|
167
167
|
{
|
|
168
168
|
provide: HttpBackend,
|
|
169
|
-
useExisting:
|
|
169
|
+
useExisting: ɵCloudflareFetchBackend
|
|
170
170
|
}
|
|
171
171
|
]);
|
|
172
172
|
}
|
|
173
|
-
export {
|
|
173
|
+
export { injectCloudflare, injectEnv, injectExecutionContext, injectRequestCf, provideCloudflare, ɵCloudflareFetchBackend };
|
|
174
174
|
|
|
175
175
|
//# sourceMappingURL=ngx-cloudflare.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngx-cloudflare.mjs","names":[],"sources":["../../../projects/ngx-cloudflare/src/lib/inject-cloudflare.ts","../../../projects/ngx-cloudflare/src/lib/cloudflare-fetch-backend.ts","../../../projects/ngx-cloudflare/src/lib/provide-cloudflare.ts"],"sourcesContent":["import { assertInInjectionContext, inject, REQUEST_CONTEXT } from '@angular/core';\nimport {\n isCloudflareContext,\n type CloudflareContext,\n type CloudflareExecutionContext,\n type CloudflareRequestCf,\n} from 'ngx-cloudflare/server';\n\nexport interface InjectCloudflareOptions {\n required?: boolean;\n}\n\nconst MISSING_CONTEXT =\n 'ngx-cloudflare: no Cloudflare request context. Bindings exist only while a request is rendered on the server ' +\n '(on Workers or in ng serve). They are null in the browser, during prerendering and at build time. ' +\n 'Check that src/server.ts uses createCloudflareRequestHandler(), or call this without { required: true } and handle null.';\n\nfunction readContext(fn: Function): CloudflareContext<any, any> | null {\n assertInInjectionContext(fn);\n const value = inject(REQUEST_CONTEXT, { optional: true });\n return isCloudflareContext(value) ? value : null;\n}\n\nfunction orThrow<T>(value: T | null, options: InjectCloudflareOptions | undefined): T | null {\n if (value === null && options?.required) {\n throw new Error(MISSING_CONTEXT);\n }\n return value;\n}\n\nexport function injectCloudflare<Env = Cloudflare.Env, Cf = CloudflareRequestCf>(options: {\n required: true;\n}): CloudflareContext<Env, Cf>;\nexport function injectCloudflare<Env = Cloudflare.Env, Cf = CloudflareRequestCf>(\n options?: InjectCloudflareOptions,\n): CloudflareContext<Env, Cf> | null;\nexport function injectCloudflare(\n options?: InjectCloudflareOptions,\n): CloudflareContext<any, any> | null {\n return orThrow(readContext(injectCloudflare), options);\n}\n\nexport function injectEnv<Env = Cloudflare.Env>(options: { required: true }): Env;\nexport function injectEnv<Env = Cloudflare.Env>(options?: InjectCloudflareOptions): Env | null;\nexport function injectEnv(options?: InjectCloudflareOptions): unknown {\n return orThrow(readContext(injectEnv)?.env ?? null, options);\n}\n\nexport function injectExecutionContext(options: { required: true }): CloudflareExecutionContext;\nexport function injectExecutionContext(\n options?: InjectCloudflareOptions,\n): CloudflareExecutionContext | null;\nexport function injectExecutionContext(\n options?: InjectCloudflareOptions,\n): CloudflareExecutionContext | null {\n return orThrow(readContext(injectExecutionContext)?.ctx ?? null, options);\n}\n\nexport function injectRequestCf<Cf = CloudflareRequestCf>(options: { required: true }): Cf;\nexport function injectRequestCf<Cf = CloudflareRequestCf>(\n options?: InjectCloudflareOptions,\n): Cf | null;\nexport function injectRequestCf(options?: InjectCloudflareOptions): unknown {\n return orThrow(readContext(injectRequestCf)?.cf ?? null, options);\n}\n","import {\n FetchBackend,\n HttpErrorResponse,\n HttpEvent,\n HttpEventType,\n HttpHeaders,\n HttpRequest,\n HttpResponse,\n} from '@angular/common/http';\nimport { inject, Injectable, REQUEST, REQUEST_CONTEXT } from '@angular/core';\nimport { Observable, type Subscription } from 'rxjs';\nimport { isCloudflareContext } from 'ngx-cloudflare/server';\n\nconst XSSI_PREFIX = /^\\)\\]\\}',?\\n/;\n\ntype DispatchPhase =\n | { kind: 'dispatching' }\n | { kind: 'fallback'; subscription: Subscription }\n | { kind: 'done' }\n | { kind: 'cancelled' };\n\nfunction toHeaders(headers: Headers): HttpHeaders {\n let result = new HttpHeaders();\n headers.forEach((value, name) => {\n result = result.append(name, value);\n });\n return result;\n}\n\nfunction toRequest(req: HttpRequest<unknown>, url: URL, signal: AbortSignal): Request {\n const headers = new Headers();\n for (const name of req.headers.keys()) {\n for (const value of req.headers.getAll(name) ?? []) {\n headers.append(name, value);\n }\n }\n if (!headers.has('accept')) {\n headers.set('accept', 'application/json, text/plain, */*');\n }\n const body = req.serializeBody();\n if (body !== null && !headers.has('content-type')) {\n const detected = req.detectContentTypeHeader();\n if (detected !== null) {\n headers.set('content-type', detected);\n }\n }\n const hasBody = body !== null && req.method !== 'GET' && req.method !== 'HEAD';\n return new Request(url, {\n method: req.method,\n headers,\n body: hasBody ? (body as BodyInit) : undefined,\n signal,\n });\n}\n\nasync function readBody(\n req: HttpRequest<unknown>,\n res: Response,\n): Promise<{ body: unknown; parseError?: unknown }> {\n switch (req.responseType) {\n case 'arraybuffer':\n return { body: await res.arrayBuffer() };\n case 'blob':\n return { body: await res.blob() };\n case 'text':\n return { body: await res.text() };\n default: {\n const text = await res.text();\n if (text === '') {\n return { body: null };\n }\n try {\n return { body: JSON.parse(text.replace(XSSI_PREFIX, '')) };\n } catch (parseError) {\n return { body: text, parseError };\n }\n }\n }\n}\n\n@Injectable()\nexport class CloudflareFetchBackend extends FetchBackend {\n private readonly cloudflare = inject(REQUEST_CONTEXT, { optional: true });\n private readonly incoming = inject(REQUEST, { optional: true });\n\n override handle(req: HttpRequest<unknown>): Observable<HttpEvent<unknown>> {\n const context = this.cloudflare;\n if (!this.incoming || !isCloudflareContext(context)) {\n return super.handle(req);\n }\n const base = new URL(this.incoming.url);\n const url = new URL(req.urlWithParams, base);\n if (url.origin !== base.origin) {\n return super.handle(req);\n }\n\n return new Observable<HttpEvent<unknown>>((observer) => {\n const aborter = new AbortController();\n let phase: DispatchPhase = { kind: 'dispatching' };\n\n const settle = (next: DispatchPhase): boolean => {\n if (phase.kind !== 'dispatching') {\n return false;\n }\n phase = next;\n return true;\n };\n\n (async () => {\n const response = await context.dispatch(toRequest(req, url, aborter.signal));\n if (phase.kind !== 'dispatching') {\n return;\n }\n if (!response) {\n const subscription = super.handle(req).subscribe(observer);\n if (!settle({ kind: 'fallback', subscription })) {\n subscription.unsubscribe();\n }\n return;\n }\n observer.next({ type: HttpEventType.Sent });\n const headers = toHeaders(response.headers);\n const { body, parseError } = await readBody(req, response);\n if (!settle({ kind: 'done' })) {\n return;\n }\n if (response.status >= 200 && response.status < 300 && !parseError) {\n observer.next(\n new HttpResponse({\n body,\n headers,\n status: response.status,\n statusText: response.statusText,\n url: url.href,\n }),\n );\n observer.complete();\n } else {\n observer.error(\n new HttpErrorResponse({\n error: parseError ? { error: parseError, text: body } : body,\n headers,\n status: response.status,\n statusText: response.statusText,\n url: url.href,\n }),\n );\n }\n })().catch((error: unknown) => {\n if (settle({ kind: 'done' })) {\n observer.error(\n new HttpErrorResponse({ error, status: 0, statusText: 'Unknown Error', url: url.href }),\n );\n }\n });\n\n return () => {\n if (phase.kind === 'fallback') {\n phase.subscription.unsubscribe();\n } else if (settle({ kind: 'cancelled' })) {\n aborter.abort();\n }\n };\n });\n }\n}\n","import { FetchBackend, HttpBackend } from '@angular/common/http';\nimport { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';\nimport { CloudflareFetchBackend } from './cloudflare-fetch-backend';\n\nexport interface ProvideCloudflareOptions {\n inProcessRequests?: boolean;\n}\n\nexport function provideCloudflare(options: ProvideCloudflareOptions = {}): EnvironmentProviders {\n if (options.inProcessRequests === false) {\n return makeEnvironmentProviders([]);\n }\n return makeEnvironmentProviders([\n CloudflareFetchBackend,\n { provide: FetchBackend, useExisting: CloudflareFetchBackend },\n { provide: HttpBackend, useExisting: CloudflareFetchBackend },\n ]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngx-cloudflare.mjs","names":[],"sources":["../../../projects/ngx-cloudflare/src/lib/inject-cloudflare.ts","../../../projects/ngx-cloudflare/src/lib/cloudflare-fetch-backend.ts","../../../projects/ngx-cloudflare/src/lib/provide-cloudflare.ts"],"sourcesContent":["import { assertInInjectionContext, inject, REQUEST_CONTEXT } from '@angular/core';\nimport {\n ɵisCloudflareContext,\n type CloudflareContext,\n type CloudflareExecutionContext,\n type CloudflareRequestCf,\n} from 'ngx-cloudflare/server';\n\nexport interface InjectCloudflareOptions {\n required?: boolean;\n}\n\nconst MISSING_CONTEXT =\n 'ngx-cloudflare: no Cloudflare request context. Bindings exist only while a request is rendered on the server ' +\n '(on Workers or in ng serve). They are null in the browser, during prerendering and at build time. ' +\n 'Check that src/server.ts uses createCloudflareRequestHandler(), or call this without { required: true } and handle null.';\n\nfunction readContext(fn: Function): CloudflareContext<any, any> | null {\n assertInInjectionContext(fn);\n const value = inject(REQUEST_CONTEXT, { optional: true });\n return ɵisCloudflareContext(value) ? value : null;\n}\n\nfunction orThrow<T>(value: T | null, options: InjectCloudflareOptions | undefined): T | null {\n if (value === null && options?.required) {\n throw new Error(MISSING_CONTEXT);\n }\n return value;\n}\n\nexport function injectCloudflare<Env = Cloudflare.Env, Cf = CloudflareRequestCf>(options: {\n required: true;\n}): CloudflareContext<Env, Cf>;\nexport function injectCloudflare<Env = Cloudflare.Env, Cf = CloudflareRequestCf>(\n options?: InjectCloudflareOptions,\n): CloudflareContext<Env, Cf> | null;\nexport function injectCloudflare(\n options?: InjectCloudflareOptions,\n): CloudflareContext<any, any> | null {\n return orThrow(readContext(injectCloudflare), options);\n}\n\nexport function injectEnv<Env = Cloudflare.Env>(options: { required: true }): Env;\nexport function injectEnv<Env = Cloudflare.Env>(options?: InjectCloudflareOptions): Env | null;\nexport function injectEnv(options?: InjectCloudflareOptions): unknown {\n return orThrow(readContext(injectEnv)?.env ?? null, options);\n}\n\nexport function injectExecutionContext(options: { required: true }): CloudflareExecutionContext;\nexport function injectExecutionContext(\n options?: InjectCloudflareOptions,\n): CloudflareExecutionContext | null;\nexport function injectExecutionContext(\n options?: InjectCloudflareOptions,\n): CloudflareExecutionContext | null {\n return orThrow(readContext(injectExecutionContext)?.ctx ?? null, options);\n}\n\nexport function injectRequestCf<Cf = CloudflareRequestCf>(options: { required: true }): Cf;\nexport function injectRequestCf<Cf = CloudflareRequestCf>(\n options?: InjectCloudflareOptions,\n): Cf | null;\nexport function injectRequestCf(options?: InjectCloudflareOptions): unknown {\n return orThrow(readContext(injectRequestCf)?.cf ?? null, options);\n}\n","import {\n FetchBackend,\n HttpErrorResponse,\n HttpEvent,\n HttpEventType,\n HttpHeaders,\n HttpRequest,\n HttpResponse,\n} from '@angular/common/http';\nimport { inject, Injectable, REQUEST, REQUEST_CONTEXT } from '@angular/core';\nimport { Observable, type Subscription } from 'rxjs';\nimport { ɵisCloudflareContext } from 'ngx-cloudflare/server';\n\nconst XSSI_PREFIX = /^\\)\\]\\}',?\\n/;\n\ntype DispatchPhase =\n | { kind: 'dispatching' }\n | { kind: 'fallback'; subscription: Subscription }\n | { kind: 'done' }\n | { kind: 'cancelled' };\n\nfunction toHeaders(headers: Headers): HttpHeaders {\n let result = new HttpHeaders();\n headers.forEach((value, name) => {\n result = result.append(name, value);\n });\n return result;\n}\n\nfunction toRequest(req: HttpRequest<unknown>, url: URL, signal: AbortSignal): Request {\n const headers = new Headers();\n for (const name of req.headers.keys()) {\n for (const value of req.headers.getAll(name) ?? []) {\n headers.append(name, value);\n }\n }\n if (!headers.has('accept')) {\n headers.set('accept', 'application/json, text/plain, */*');\n }\n const body = req.serializeBody();\n if (body !== null && !headers.has('content-type')) {\n const detected = req.detectContentTypeHeader();\n if (detected !== null) {\n headers.set('content-type', detected);\n }\n }\n const hasBody = body !== null && req.method !== 'GET' && req.method !== 'HEAD';\n return new Request(url, {\n method: req.method,\n headers,\n body: hasBody ? (body as BodyInit) : undefined,\n signal,\n });\n}\n\nasync function readBody(\n req: HttpRequest<unknown>,\n res: Response,\n): Promise<{ body: unknown; parseError?: unknown }> {\n switch (req.responseType) {\n case 'arraybuffer':\n return { body: await res.arrayBuffer() };\n case 'blob':\n return { body: await res.blob() };\n case 'text':\n return { body: await res.text() };\n default: {\n const text = await res.text();\n if (text === '') {\n return { body: null };\n }\n try {\n return { body: JSON.parse(text.replace(XSSI_PREFIX, '')) };\n } catch (parseError) {\n return { body: text, parseError };\n }\n }\n }\n}\n\n@Injectable()\nexport class ɵCloudflareFetchBackend extends FetchBackend {\n private readonly cloudflare = inject(REQUEST_CONTEXT, { optional: true });\n private readonly incoming = inject(REQUEST, { optional: true });\n\n override handle(req: HttpRequest<unknown>): Observable<HttpEvent<unknown>> {\n const context = this.cloudflare;\n if (!this.incoming || !ɵisCloudflareContext(context)) {\n return super.handle(req);\n }\n const base = new URL(this.incoming.url);\n const url = new URL(req.urlWithParams, base);\n if (url.origin !== base.origin) {\n return super.handle(req);\n }\n\n return new Observable<HttpEvent<unknown>>((observer) => {\n const aborter = new AbortController();\n let phase: DispatchPhase = { kind: 'dispatching' };\n\n const settle = (next: DispatchPhase): boolean => {\n if (phase.kind !== 'dispatching') {\n return false;\n }\n phase = next;\n return true;\n };\n\n (async () => {\n const response = await context.dispatch(toRequest(req, url, aborter.signal));\n if (phase.kind !== 'dispatching') {\n return;\n }\n if (!response) {\n const subscription = super.handle(req).subscribe(observer);\n if (!settle({ kind: 'fallback', subscription })) {\n subscription.unsubscribe();\n }\n return;\n }\n observer.next({ type: HttpEventType.Sent });\n const headers = toHeaders(response.headers);\n const { body, parseError } = await readBody(req, response);\n if (!settle({ kind: 'done' })) {\n return;\n }\n if (response.status >= 200 && response.status < 300 && !parseError) {\n observer.next(\n new HttpResponse({\n body,\n headers,\n status: response.status,\n statusText: response.statusText,\n url: url.href,\n }),\n );\n observer.complete();\n } else {\n observer.error(\n new HttpErrorResponse({\n error: parseError ? { error: parseError, text: body } : body,\n headers,\n status: response.status,\n statusText: response.statusText,\n url: url.href,\n }),\n );\n }\n })().catch((error: unknown) => {\n if (settle({ kind: 'done' })) {\n observer.error(\n new HttpErrorResponse({ error, status: 0, statusText: 'Unknown Error', url: url.href }),\n );\n }\n });\n\n return () => {\n if (phase.kind === 'fallback') {\n phase.subscription.unsubscribe();\n } else if (settle({ kind: 'cancelled' })) {\n aborter.abort();\n }\n };\n });\n }\n}\n","import { FetchBackend, HttpBackend } from '@angular/common/http';\nimport { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';\nimport { ɵCloudflareFetchBackend } from './cloudflare-fetch-backend';\n\nexport interface ProvideCloudflareOptions {\n inProcessRequests?: boolean;\n}\n\nexport function provideCloudflare(options: ProvideCloudflareOptions = {}): EnvironmentProviders {\n if (options.inProcessRequests === false) {\n return makeEnvironmentProviders([]);\n }\n return makeEnvironmentProviders([\n ɵCloudflareFetchBackend,\n { provide: FetchBackend, useExisting: ɵCloudflareFetchBackend },\n { provide: HttpBackend, useExisting: ɵCloudflareFetchBackend },\n ]);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -3,9 +3,7 @@ declare global {
|
|
|
3
3
|
interface Env {}
|
|
4
4
|
}
|
|
5
5
|
}
|
|
6
|
-
export declare const CLOUDFLARE_CONTEXT_MARKER: unique symbol;
|
|
7
|
-
export declare const DEFAULT_ALLOWED_HOSTS: readonly string[];
|
|
8
|
-
export declare const DEFAULT_API_PREFIX = "/api/";
|
|
6
|
+
export declare const ɵCLOUDFLARE_CONTEXT_MARKER: unique symbol;
|
|
9
7
|
export interface CloudflareExecutionContext {
|
|
10
8
|
waitUntil(promise: Promise<unknown>): void;
|
|
11
9
|
passThroughOnException(): void;
|
|
@@ -20,7 +18,7 @@ export interface CloudflarePlatform<Env = Cloudflare.Env, Cf = CloudflareRequest
|
|
|
20
18
|
export interface CloudflareContext<Env = Cloudflare.Env, Cf = CloudflareRequestCf> extends CloudflarePlatform<Env, Cf> {
|
|
21
19
|
readonly source: CloudflareContextSource;
|
|
22
20
|
readonly dispatch: (request: Request) => Promise<Response | null>;
|
|
23
|
-
readonly [CLOUDFLARE_CONTEXT_MARKER]: true;
|
|
21
|
+
readonly [ɵCLOUDFLARE_CONTEXT_MARKER]: true;
|
|
24
22
|
}
|
|
25
23
|
export type ApiResult = Response | null | undefined;
|
|
26
24
|
export type ApiHandler<Env = Cloudflare.Env> = (request: Request, env: Env, ctx: CloudflareExecutionContext) => ApiResult | Promise<ApiResult>;
|
|
@@ -32,21 +30,25 @@ export interface CloudflareHandlerOptions<Env = Cloudflare.Env> {
|
|
|
32
30
|
api?: ApiOption<Env>;
|
|
33
31
|
apiPrefix?: string | false;
|
|
34
32
|
stripProxyHeaders?: boolean;
|
|
33
|
+
cachePages?: boolean;
|
|
35
34
|
}
|
|
36
35
|
export type RenderFunction = (request: Request, context: CloudflareContext<any, any> | undefined) => Promise<Response | null>;
|
|
37
36
|
export type DevPlatformLoader = () => Promise<CloudflarePlatform<any, any> | undefined> | undefined;
|
|
38
37
|
export type CloudflareFetchHandler<Env = Cloudflare.Env> = (request: Request, env?: Env, ctx?: CloudflareExecutionContext) => Promise<Response>;
|
|
39
38
|
export declare function isInProcessRequest(request: Request): boolean;
|
|
40
|
-
export declare function
|
|
41
|
-
export declare const ALLOWED_HOSTS_VAR = "ALLOWED_HOSTS";
|
|
39
|
+
export declare function ɵisCloudflareContext(value: unknown): value is CloudflareContext<any, any>;
|
|
42
40
|
export declare function hostsFromEnv(env: unknown): string[];
|
|
43
41
|
export declare function resolveAllowedHosts(extra?: readonly string[]): string[];
|
|
44
42
|
export declare function isHostAllowed(hostname: string, allowedHosts: readonly string[]): boolean;
|
|
45
|
-
export declare function hostRefusal(hostname: string): Response;
|
|
46
|
-
export declare function isProxyHeader(name: string): boolean;
|
|
47
43
|
export declare function stripProxyHeaders(request: Request): Request;
|
|
48
|
-
export
|
|
49
|
-
|
|
44
|
+
export interface PageCache {
|
|
45
|
+
match(request: Request): Promise<Response | undefined>;
|
|
46
|
+
put(request: Request, response: Response): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
export declare function isCacheablePageRequest(request: Request): boolean;
|
|
49
|
+
export declare function isCacheablePageResponse(response: Response): boolean;
|
|
50
|
+
export declare function ɵtoApiHandler<Env>(api: ApiOption<Env> | undefined): ApiHandler<Env> | undefined;
|
|
51
|
+
export declare function ɵcallInProcess<Env>(handler: ApiHandler<Env>, request: Request, env: Env, ctx: CloudflareExecutionContext): Promise<Response | null>;
|
|
50
52
|
export declare function createCloudflareFetch<Env = Cloudflare.Env>(render: RenderFunction, options?: CloudflareHandlerOptions<Env>, loadDevPlatform?: DevPlatformLoader): CloudflareFetchHandler<Env>;
|
|
51
53
|
export interface DevPlatformOptions {
|
|
52
54
|
configPath?: string;
|
|
@@ -11,6 +11,6 @@ export interface CloudflareTestingOptions<Env = Cloudflare.Env, Cf = CloudflareR
|
|
|
11
11
|
export interface TestExecutionContext extends CloudflareExecutionContext {
|
|
12
12
|
readonly waitUntilPromises: Promise<unknown>[];
|
|
13
13
|
}
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function createCloudflareTestExecutionContext(overrides?: Partial<CloudflareExecutionContext>): TestExecutionContext;
|
|
15
15
|
export declare function createCloudflareTestContext<Env = Cloudflare.Env, Cf = CloudflareRequestCf>(options?: CloudflareTestingOptions<Env, Cf>): CloudflareContext<Env, Cf>;
|
|
16
16
|
export declare function provideCloudflareTesting<Env = Cloudflare.Env, Cf = CloudflareRequestCf>(options?: CloudflareTestingOptions<Env, Cf>): EnvironmentProviders;
|
|
@@ -26,11 +26,11 @@ export interface ProvideCloudflareOptions {
|
|
|
26
26
|
inProcessRequests?: boolean;
|
|
27
27
|
}
|
|
28
28
|
export declare function provideCloudflare(options?: ProvideCloudflareOptions): EnvironmentProviders;
|
|
29
|
-
export declare class
|
|
29
|
+
export declare class ɵCloudflareFetchBackend extends FetchBackend {
|
|
30
30
|
private readonly cloudflare;
|
|
31
31
|
private readonly incoming;
|
|
32
32
|
handle(req: HttpRequest<unknown>): Observable<HttpEvent<unknown>>;
|
|
33
|
-
static ɵfac: i0.ɵɵFactoryDeclaration
|
|
33
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ɵCloudflareFetchBackend, never>;
|
|
34
34
|
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
35
35
|
}
|
|
36
36
|
export type { CloudflareContext, CloudflareContextSource, CloudflareExecutionContext, CloudflareRequestCf };
|