@httpquery/edge 0.1.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.
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Cloudflare Workers helpers for HTTP QUERY (RFC 10008).
3
+ *
4
+ * What Cloudflare/Akamai need: cache QUERY by URL+body, not URL alone.
5
+ */
6
+ import { QUERY_METHOD } from "@httpquery/core";
7
+ import { toCacheApiRequest, queryCacheControl, cdnCacheKey } from "./index.js";
8
+ import { type AutoQueryCacheOptions } from "./auto-cache.js";
9
+ export interface HandleQueryOptions {
10
+ /** Cache API binding (default caches.default). */
11
+ cache?: {
12
+ match: (r: Request) => Promise<Response | undefined>;
13
+ put: (r: Request, res: Response) => Promise<void>;
14
+ };
15
+ /** Shared cache TTL seconds. Default 60. */
16
+ sMaxAge?: number;
17
+ /** Accept-Query media types. */
18
+ acceptQuery?: string[];
19
+ /** Also accept POST as dual-route (migration). Default true. */
20
+ allowPost?: boolean;
21
+ /** CORS origin reflection. Default true. */
22
+ cors?: boolean;
23
+ /**
24
+ * Full auto body-keyed QUERY cache platform (RFC 10008 §2.7).
25
+ * Default true — closes CF Cache API gap (body not in default key).
26
+ */
27
+ autoCache?: boolean;
28
+ /** Extra options for auto platform */
29
+ autoCacheOptions?: Omit<AutoQueryCacheOptions, "cache" | "sMaxAge">;
30
+ }
31
+ /**
32
+ * Wrap a Worker handler with QUERY caching + CORS + dual POST.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * export default {
37
+ * fetch: handleQuery(async (request, env, ctx) => {
38
+ * const body = await request.json()
39
+ * return Response.json({ results: search(body) })
40
+ * }, { sMaxAge: 120 })
41
+ * }
42
+ * ```
43
+ */
44
+ export declare function handleQuery(handler: (request: Request, env: any, ctx: any) => Promise<Response> | Response, options?: HandleQueryOptions): (request: Request, env: any, ctx: any) => Promise<Response>;
45
+ /**
46
+ * Minimal Worker export factory for product search style APIs.
47
+ */
48
+ export declare function createQueryWorker(options: {
49
+ search: (body: any, env: any) => Promise<unknown> | unknown;
50
+ sMaxAge?: number;
51
+ /** Full auto body-keyed cache (default true) */
52
+ autoCache?: boolean;
53
+ }): {
54
+ fetch: (request: Request, env: any, ctx: any) => Promise<Response>;
55
+ };
56
+ export { toCacheApiRequest, cdnCacheKey, queryCacheControl, QUERY_METHOD };
57
+ export { withAutoQueryCache, createAutoQueryPlatform, createMemoryCache } from "./auto-cache.js";
58
+ //# sourceMappingURL=cloudflare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../src/cloudflare.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAA0C,MAAM,iBAAiB,CAAC;AACvF,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAsB,WAAW,EAAE,MAAM,YAAY,CAAC;AACnG,OAAO,EAAsB,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAEjF,MAAM,WAAW,kBAAkB;IACjC,kDAAkD;IAElD,KAAK,CAAC,EAAE;QACN,KAAK,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;QACrD,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACnD,CAAC;IACF,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sCAAsC;IACtC,gBAAgB,CAAC,EAAE,IAAI,CAAC,qBAAqB,EAAE,OAAO,GAAG,SAAS,CAAC,CAAC;CACrE;AAOD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,EAC/E,OAAO,GAAE,kBAAuB,GAC/B,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,CAyH7D;AAgCD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,GAAG;IAAE,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAAE,CAqBzE;AAED,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Cloudflare Workers helpers for HTTP QUERY (RFC 10008).
3
+ *
4
+ * What Cloudflare/Akamai need: cache QUERY by URL+body, not URL alone.
5
+ */
6
+ import { QUERY_METHOD, ACCEPT_QUERY_HEADER, formatAcceptQuery } from "@httpquery/core";
7
+ import { toCacheApiRequest, queryCacheControl, queryOriginHeaders, cdnCacheKey } from "./index.js";
8
+ import { withAutoQueryCache } from "./auto-cache.js";
9
+ function isQueryOrPost(method, allowPost) {
10
+ const m = method.toUpperCase();
11
+ return m === QUERY_METHOD || (allowPost && m === "POST");
12
+ }
13
+ /**
14
+ * Wrap a Worker handler with QUERY caching + CORS + dual POST.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * export default {
19
+ * fetch: handleQuery(async (request, env, ctx) => {
20
+ * const body = await request.json()
21
+ * return Response.json({ results: search(body) })
22
+ * }, { sMaxAge: 120 })
23
+ * }
24
+ * ```
25
+ */
26
+ export function handleQuery(handler, options = {}) {
27
+ const allowPost = options.allowPost !== false;
28
+ const cors = options.cors !== false;
29
+ const acceptQuery = options.acceptQuery ?? ["application/json"];
30
+ const sMaxAge = options.sMaxAge ?? 60;
31
+ const core = async (request, env, ctx) => {
32
+ const method = request.method.toUpperCase();
33
+ // CORS preflight
34
+ if (method === "OPTIONS") {
35
+ return new Response(null, {
36
+ status: 204,
37
+ headers: corsHeaders(request, acceptQuery, cors),
38
+ });
39
+ }
40
+ if (!isQueryOrPost(method, allowPost)) {
41
+ return new Response("Method Not Allowed", {
42
+ status: 405,
43
+ headers: {
44
+ Allow: `GET, HEAD, POST, ${QUERY_METHOD}, OPTIONS`,
45
+ ...corsHeaders(request, acceptQuery, cors),
46
+ },
47
+ });
48
+ }
49
+ const response = await handler(request, env, ctx);
50
+ const headers = new Headers(response.headers);
51
+ const originHdrs = queryOriginHeaders({
52
+ acceptQuery,
53
+ cacheControl: queryCacheControl({ sMaxAge, maxAge: 0, staleWhileRevalidate: 30 }),
54
+ });
55
+ for (const [k, v] of Object.entries(originHdrs)) {
56
+ if (!headers.has(k))
57
+ headers.set(k, v);
58
+ }
59
+ applyCors(headers, request, cors);
60
+ if (!headers.has("X-HTTP-Query-Cache")) {
61
+ headers.set("X-HTTP-Query-Cache", "BYPASS");
62
+ }
63
+ return new Response(response.body, {
64
+ status: response.status,
65
+ statusText: response.statusText,
66
+ headers,
67
+ });
68
+ };
69
+ // Full auto body-keyed platform (default) — fills CF Cache API gap
70
+ if (options.autoCache !== false) {
71
+ return withAutoQueryCache(core, {
72
+ cache: options.cache,
73
+ sMaxAge,
74
+ softTtlSeconds: Math.max(1, Math.floor(sMaxAge / 2)),
75
+ ...options.autoCacheOptions,
76
+ });
77
+ }
78
+ // Legacy path without auto platform
79
+ return async (request, env, ctx) => {
80
+ const method = request.method.toUpperCase();
81
+ if (method === "OPTIONS") {
82
+ return new Response(null, {
83
+ status: 204,
84
+ headers: corsHeaders(request, acceptQuery, cors),
85
+ });
86
+ }
87
+ if (!isQueryOrPost(method, allowPost)) {
88
+ return new Response("Method Not Allowed", {
89
+ status: 405,
90
+ headers: {
91
+ Allow: `GET, HEAD, POST, ${QUERY_METHOD}, OPTIONS`,
92
+ ...corsHeaders(request, acceptQuery, cors),
93
+ },
94
+ });
95
+ }
96
+ const cacheable = method === QUERY_METHOD;
97
+ const cacheStore = options.cache ?? globalThis.caches?.default;
98
+ if (cacheable && cacheStore) {
99
+ const cacheReq = await toCacheApiRequest(request);
100
+ const hit = await cacheStore.match(cacheReq);
101
+ if (hit) {
102
+ const h = new Headers(hit.headers);
103
+ h.set("X-HTTP-Query-Cache", "HIT");
104
+ applyCors(h, request, cors);
105
+ return new Response(hit.body, { status: hit.status, headers: h });
106
+ }
107
+ }
108
+ const response = await handler(request, env, ctx);
109
+ const headers = new Headers(response.headers);
110
+ const originHdrs = queryOriginHeaders({
111
+ acceptQuery,
112
+ cacheControl: queryCacheControl({ sMaxAge, maxAge: 0, staleWhileRevalidate: 30 }),
113
+ });
114
+ for (const [k, v] of Object.entries(originHdrs)) {
115
+ if (!headers.has(k))
116
+ headers.set(k, v);
117
+ }
118
+ applyCors(headers, request, cors);
119
+ headers.set("X-HTTP-Query-Cache", "MISS");
120
+ const out = new Response(response.body, {
121
+ status: response.status,
122
+ statusText: response.statusText,
123
+ headers,
124
+ });
125
+ if (cacheable && cacheStore && response.ok) {
126
+ const cacheReq = await toCacheApiRequest(request);
127
+ const p = cacheStore.put(cacheReq, out.clone());
128
+ if (ctx?.waitUntil)
129
+ ctx.waitUntil(p);
130
+ else
131
+ await p;
132
+ }
133
+ return out;
134
+ };
135
+ }
136
+ function corsHeaders(request, acceptQuery, cors) {
137
+ const h = {
138
+ Allow: `GET, HEAD, POST, ${QUERY_METHOD}, OPTIONS`,
139
+ [ACCEPT_QUERY_HEADER]: formatAcceptQuery(acceptQuery),
140
+ };
141
+ if (cors) {
142
+ h["Access-Control-Allow-Origin"] = request.headers.get("Origin") ?? "*";
143
+ h["Access-Control-Allow-Methods"] = `GET, HEAD, POST, ${QUERY_METHOD}, OPTIONS`;
144
+ h["Access-Control-Allow-Headers"] =
145
+ "Content-Type, Accept, Authorization, X-HTTP-Method-Override, Accept-Query";
146
+ h["Access-Control-Expose-Headers"] =
147
+ "Accept-Query, Location, Content-Location, Allow, X-HTTP-Query-Cache";
148
+ h["Access-Control-Max-Age"] = "300";
149
+ }
150
+ return h;
151
+ }
152
+ function applyCors(headers, request, cors) {
153
+ if (!cors)
154
+ return;
155
+ const origin = request.headers.get("Origin");
156
+ if (origin)
157
+ headers.set("Access-Control-Allow-Origin", origin);
158
+ else if (!headers.has("Access-Control-Allow-Origin")) {
159
+ headers.set("Access-Control-Allow-Origin", "*");
160
+ }
161
+ }
162
+ /**
163
+ * Minimal Worker export factory for product search style APIs.
164
+ */
165
+ export function createQueryWorker(options) {
166
+ return {
167
+ fetch: handleQuery(async (request, env) => {
168
+ const body = await request.json().catch(() => ({}));
169
+ const results = await options.search(body, env);
170
+ return Response.json({ results, method: request.method }, {
171
+ headers: {
172
+ "Cache-Control": queryCacheControl({
173
+ sMaxAge: options.sMaxAge ?? 60,
174
+ maxAge: 0,
175
+ }),
176
+ },
177
+ });
178
+ }, { sMaxAge: options.sMaxAge, autoCache: options.autoCache !== false }),
179
+ };
180
+ }
181
+ export { toCacheApiRequest, cdnCacheKey, queryCacheControl, QUERY_METHOD };
182
+ export { withAutoQueryCache, createAutoQueryPlatform, createMemoryCache } from "./auto-cache.js";
183
+ //# sourceMappingURL=cloudflare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../src/cloudflare.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @httpquery/edge — CDN-correct cache keys for HTTP QUERY (RFC 10008 §2.7).
3
+ *
4
+ * Cloudflare / Akamai / Fastly need: cache key MUST include request body.
5
+ * This package builds those keys and helpers for edge runtimes.
6
+ */
7
+ import { QUERY_METHOD, ACCEPT_QUERY_HEADER, cacheKey, cacheKeySync, formatAcceptQuery, parseAcceptQuery } from "@httpquery/core";
8
+ export { QUERY_METHOD, ACCEPT_QUERY_HEADER, cacheKey, cacheKeySync, formatAcceptQuery, parseAcceptQuery, };
9
+ export interface CdnCacheKeyInput {
10
+ /** Full request URL (or path + host). */
11
+ url: string | URL;
12
+ /** Raw body string or bytes. */
13
+ body?: string | ArrayBuffer | Uint8Array | null;
14
+ /** Content-Type (included in key — different formats ≠ same query). */
15
+ contentType?: string;
16
+ /** Optional Vary-like headers to fold into the key. */
17
+ varyHeaders?: Record<string, string | null | undefined>;
18
+ }
19
+ /**
20
+ * Build a CDN-safe cache key for a QUERY request.
21
+ * RFC 10008: key MUST incorporate request content + related metadata.
22
+ *
23
+ * Use as:
24
+ * - Cloudflare Cache API: `new Request(cacheUrl)` where cacheUrl embeds this key
25
+ * - Custom KV key
26
+ * - Redis key at origin
27
+ */
28
+ export declare function cdnCacheKey(input: CdnCacheKeyInput): Promise<string>;
29
+ /** Sync FNV variant for environments without crypto.subtle. */
30
+ export declare function cdnCacheKeySync(input: CdnCacheKeyInput): string;
31
+ /**
32
+ * Turn a QUERY request into a synthetic GET Request URL for Cache API.
33
+ * Many CDNs/Cache APIs key primarily on GET URL — we embed body hash in the path.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * const cacheReq = await toCacheApiRequest(request)
38
+ * const hit = await caches.default.match(cacheReq)
39
+ * ```
40
+ */
41
+ export declare function toCacheApiRequest(request: Request, options?: {
42
+ cacheHost?: string;
43
+ prefix?: string;
44
+ }): Promise<Request>;
45
+ /**
46
+ * Recommended Cache-Control for QUERY responses at edge.
47
+ * - public + s-maxage: CDN may store
48
+ * - Vary note: body is in key already; still vary on Accept if you negotiate
49
+ */
50
+ export declare function queryCacheControl(options?: {
51
+ /** Shared CDN max age seconds. Default 60. */
52
+ sMaxAge?: number;
53
+ /** Browser max age. Default 0 (revalidate). */
54
+ maxAge?: number;
55
+ /** private = only browser/app cache, not shared CDN */
56
+ private?: boolean;
57
+ /** Immutable-ish short lived search */
58
+ staleWhileRevalidate?: number;
59
+ }): string;
60
+ /** Headers a QUERY origin should set for CDN friendliness. */
61
+ export declare function queryOriginHeaders(options?: {
62
+ acceptQuery?: string[];
63
+ cacheControl?: string;
64
+ location?: string;
65
+ contentLocation?: string;
66
+ }): Record<string, string>;
67
+ export { akamaiQueryCacheId, akamaiOpsMarkdown, AKAMAI_EDGEWORKER_QUERY_SAMPLE, AKAMAI_PROPERTY_CHECKLIST, } from "./akamai.js";
68
+ export { OTEL_HTTP_METHOD_QUERY, querySpanAttributes, withQuerySpan, queryLogFields, } from "./otel.js";
69
+ export { withAutoQueryCache, createAutoQueryPlatform, createMemoryCache, type AutoQueryCacheOptions, } from "./auto-cache.js";
70
+ export { withCfNativeBodyCache, createCfNativeQueryWorker, cfBodyCacheKeyRequest, getCfBodyCacheMetrics, resetCfBodyCacheMetrics, type CfNativeBodyCacheOptions, type CfBodyCacheMetrics, } from "./cf-body-cache.js";
71
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,GACjB,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;IAClB,gCAAgC;IAChC,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,IAAI,CAAC;IAChD,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACzD;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAW1E;AAED,+DAA+D;AAC/D,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAqB/D;AAED;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD,OAAO,CAAC,OAAO,CAAC,CAiBlB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE;IACP,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uCAAuC;IACvC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC1B,GACL,MAAM,CAcR;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAChC,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CACrB,GACL,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAaxB;AAGD,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,cAAc,GACf,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,KAAK,qBAAqB,GAC3B,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @httpquery/edge — CDN-correct cache keys for HTTP QUERY (RFC 10008 §2.7).
3
+ *
4
+ * Cloudflare / Akamai / Fastly need: cache key MUST include request body.
5
+ * This package builds those keys and helpers for edge runtimes.
6
+ */
7
+ import { QUERY_METHOD, ACCEPT_QUERY_HEADER, cacheKey, cacheKeySync, formatAcceptQuery, parseAcceptQuery, } from "@httpquery/core";
8
+ export { QUERY_METHOD, ACCEPT_QUERY_HEADER, cacheKey, cacheKeySync, formatAcceptQuery, parseAcceptQuery, };
9
+ /**
10
+ * Build a CDN-safe cache key for a QUERY request.
11
+ * RFC 10008: key MUST incorporate request content + related metadata.
12
+ *
13
+ * Use as:
14
+ * - Cloudflare Cache API: `new Request(cacheUrl)` where cacheUrl embeds this key
15
+ * - Custom KV key
16
+ * - Redis key at origin
17
+ */
18
+ export async function cdnCacheKey(input) {
19
+ const extra = {};
20
+ if (input.contentType) {
21
+ extra["content-type"] = input.contentType.split(";")[0]?.trim().toLowerCase() ?? "";
22
+ }
23
+ if (input.varyHeaders) {
24
+ for (const [k, v] of Object.entries(input.varyHeaders)) {
25
+ if (v != null && v !== "")
26
+ extra[k.toLowerCase()] = v;
27
+ }
28
+ }
29
+ return cacheKey(input.url, input.body ?? "", Object.keys(extra).length ? extra : undefined);
30
+ }
31
+ /** Sync FNV variant for environments without crypto.subtle. */
32
+ export function cdnCacheKeySync(input) {
33
+ const ct = input.contentType?.split(";")[0]?.trim().toLowerCase() ?? "";
34
+ const body = typeof input.body === "string"
35
+ ? input.body
36
+ : input.body == null
37
+ ? ""
38
+ : new TextDecoder().decode(input.body instanceof ArrayBuffer ? new Uint8Array(input.body) : input.body);
39
+ const vary = input.varyHeaders
40
+ ? Object.entries(input.varyHeaders)
41
+ .filter(([, v]) => v != null && v !== "")
42
+ .map(([k, v]) => `${k.toLowerCase()}:${v}`)
43
+ .sort()
44
+ .join("&")
45
+ : "";
46
+ return cacheKeySync(`${typeof input.url === "string" ? input.url : input.url.toString()}|ct=${ct}|v=${vary}`, body);
47
+ }
48
+ /**
49
+ * Turn a QUERY request into a synthetic GET Request URL for Cache API.
50
+ * Many CDNs/Cache APIs key primarily on GET URL — we embed body hash in the path.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const cacheReq = await toCacheApiRequest(request)
55
+ * const hit = await caches.default.match(cacheReq)
56
+ * ```
57
+ */
58
+ export async function toCacheApiRequest(request, options = {}) {
59
+ const url = new URL(request.url);
60
+ const body = await request.clone().text();
61
+ const ct = request.headers.get("content-type") ?? "application/json";
62
+ const key = await cdnCacheKey({
63
+ url: `${url.origin}${url.pathname}`,
64
+ body,
65
+ contentType: ct,
66
+ varyHeaders: {
67
+ accept: request.headers.get("accept"),
68
+ authorization: request.headers.get("authorization") ? "1" : "0",
69
+ },
70
+ });
71
+ const host = options.cacheHost ?? url.host;
72
+ const prefix = options.prefix ?? "/__http_query_cache";
73
+ const cacheUrl = `https://${host}${prefix}/${key}`;
74
+ return new Request(cacheUrl, { method: "GET" });
75
+ }
76
+ /**
77
+ * Recommended Cache-Control for QUERY responses at edge.
78
+ * - public + s-maxage: CDN may store
79
+ * - Vary note: body is in key already; still vary on Accept if you negotiate
80
+ */
81
+ export function queryCacheControl(options = {}) {
82
+ const parts = [];
83
+ if (options.private) {
84
+ parts.push("private");
85
+ parts.push(`max-age=${options.maxAge ?? 0}`);
86
+ }
87
+ else {
88
+ parts.push("public");
89
+ parts.push(`max-age=${options.maxAge ?? 0}`);
90
+ parts.push(`s-maxage=${options.sMaxAge ?? 60}`);
91
+ }
92
+ if (options.staleWhileRevalidate != null) {
93
+ parts.push(`stale-while-revalidate=${options.staleWhileRevalidate}`);
94
+ }
95
+ return parts.join(", ");
96
+ }
97
+ /** Headers a QUERY origin should set for CDN friendliness. */
98
+ export function queryOriginHeaders(options = {}) {
99
+ const h = {
100
+ "Cache-Control": options.cacheControl ?? queryCacheControl({ sMaxAge: 60, maxAge: 0 }),
101
+ // Body is part of key; Accept still matters for content negotiation
102
+ Vary: "Accept, Accept-Encoding",
103
+ };
104
+ if (options.acceptQuery?.length) {
105
+ h[ACCEPT_QUERY_HEADER] = formatAcceptQuery(options.acceptQuery);
106
+ }
107
+ if (options.location)
108
+ h.Location = options.location;
109
+ if (options.contentLocation)
110
+ h["Content-Location"] = options.contentLocation;
111
+ h.Allow = `GET, HEAD, POST, ${QUERY_METHOD}, OPTIONS`;
112
+ return h;
113
+ }
114
+ // Akamai ops + EdgeWorker sketches
115
+ export { akamaiQueryCacheId, akamaiOpsMarkdown, AKAMAI_EDGEWORKER_QUERY_SAMPLE, AKAMAI_PROPERTY_CHECKLIST, } from "./akamai.js";
116
+ // OpenTelemetry / logging attributes for QUERY
117
+ export { OTEL_HTTP_METHOD_QUERY, querySpanAttributes, withQuerySpan, queryLogFields, } from "./otel.js";
118
+ // Full auto body-keyed QUERY cache platform (CF Cache API gap closed)
119
+ export { withAutoQueryCache, createAutoQueryPlatform, createMemoryCache, } from "./auto-cache.js";
120
+ // Strongest Workers body-key platform until CF ships native body keys (workerd#6849)
121
+ export { withCfNativeBodyCache, createCfNativeQueryWorker, cfBodyCacheKeyRequest, getCfBodyCacheMetrics, resetCfBodyCacheMetrics, } from "./cf-body-cache.js";
122
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,GACjB,CAAC;AAaF;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAuB;IACvD,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QACtB,KAAK,CAAC,cAAc,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IACtF,CAAC;IACD,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QACtB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;YACvD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE;gBAAE,KAAK,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAC9F,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,eAAe,CAAC,KAAuB;IACrD,MAAM,EAAE,GAAG,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IACxE,MAAM,IAAI,GACR,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;QAC5B,CAAC,CAAC,KAAK,CAAC,IAAI;QACZ,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI;YAClB,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CACtB,KAAK,CAAC,IAAI,YAAY,WAAW,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAC5E,CAAC;IACV,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW;QAC5B,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC;aAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;aACxC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC;aAC1C,IAAI,EAAE;aACN,IAAI,CAAC,GAAG,CAAC;QACd,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,YAAY,CACjB,GAAG,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,IAAI,EAAE,EACxF,IAAI,CACL,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAgB,EAChB,UAAmD,EAAE;IAErD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC;IAC1C,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,kBAAkB,CAAC;IACrE,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC;QAC5B,GAAG,EAAE,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,EAAE;QACnC,IAAI;QACJ,WAAW,EAAE,EAAE;QACf,WAAW,EAAE;YACX,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;YACrC,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;SAChE;KACF,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC;IAC3C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,qBAAqB,CAAC;IACvD,MAAM,QAAQ,GAAG,WAAW,IAAI,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC;IACnD,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;AAClD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UASI,EAAE;IAEN,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,WAAW,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,WAAW,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7C,KAAK,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,OAAO,CAAC,oBAAoB,IAAI,IAAI,EAAE,CAAC;QACzC,KAAK,CAAC,IAAI,CAAC,0BAA0B,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,kBAAkB,CAChC,UAKI,EAAE;IAEN,MAAM,CAAC,GAA2B;QAChC,eAAe,EAAE,OAAO,CAAC,YAAY,IAAI,iBAAiB,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QACtF,oEAAoE;QACpE,IAAI,EAAE,yBAAyB;KAChC,CAAC;IACF,IAAI,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;QAChC,CAAC,CAAC,mBAAmB,CAAC,GAAG,iBAAiB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IACpD,IAAI,OAAO,CAAC,eAAe;QAAE,CAAC,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC;IAC7E,CAAC,CAAC,KAAK,GAAG,oBAAoB,YAAY,WAAW,CAAC;IACtD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,mCAAmC;AACnC,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AAErB,+CAA+C;AAC/C,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,cAAc,GACf,MAAM,WAAW,CAAC;AAEnB,sEAAsE;AACtE,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,GAElB,MAAM,iBAAiB,CAAC;AAEzB,qFAAqF;AACrF,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,GAGxB,MAAM,oBAAoB,CAAC"}
package/dist/otel.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * OpenTelemetry helpers for HTTP QUERY.
3
+ *
4
+ * Many OTel agents map unknown methods to `_OTHER` — dashboards lose QUERY.
5
+ * Set these attributes so metrics/traces stay queryable.
6
+ */
7
+ export declare const OTEL_HTTP_METHOD_QUERY: "QUERY";
8
+ /**
9
+ * Semantic attributes for a QUERY span / metric dimension.
10
+ * Aligns with OTel HTTP semantic conventions + explicit method.
11
+ */
12
+ export declare function querySpanAttributes(input: {
13
+ url: string;
14
+ statusCode?: number;
15
+ usedFallback?: boolean;
16
+ methodUsed?: string;
17
+ bodyBytes?: number;
18
+ }): Record<string, string | number | boolean>;
19
+ /**
20
+ * Wrap a QUERY call with a generic tracer-like API (no hard dep on @opentelemetry/api).
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * await withQuerySpan(tracer, { name: 'QUERY /search', url }, () => queryJson(url, init))
25
+ * ```
26
+ */
27
+ export declare function withQuerySpan<T>(tracer: {
28
+ startActiveSpan: (name: string, fn: (span: any) => Promise<T>) => Promise<T>;
29
+ }, opts: {
30
+ name: string;
31
+ url: string;
32
+ bodyBytes?: number;
33
+ }, fn: () => Promise<T>): Promise<T>;
34
+ /** Log-line friendly label so log scrapers don't drop QUERY */
35
+ export declare function queryLogFields(meta: {
36
+ methodUsed?: string;
37
+ status?: number;
38
+ usedFallback?: boolean;
39
+ }): Record<string, unknown>;
40
+ //# sourceMappingURL=otel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.d.ts","sourceRoot":"","sources":["../src/otel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,sBAAsB,EAAG,OAAgB,CAAC;AAEvD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAU5C;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,MAAM,EAAE;IAAE,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;CAAE,EACxF,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EACvD,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAuBZ;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAO1B"}
package/dist/otel.js ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * OpenTelemetry helpers for HTTP QUERY.
3
+ *
4
+ * Many OTel agents map unknown methods to `_OTHER` — dashboards lose QUERY.
5
+ * Set these attributes so metrics/traces stay queryable.
6
+ */
7
+ export const OTEL_HTTP_METHOD_QUERY = "QUERY";
8
+ /**
9
+ * Semantic attributes for a QUERY span / metric dimension.
10
+ * Aligns with OTel HTTP semantic conventions + explicit method.
11
+ */
12
+ export function querySpanAttributes(input) {
13
+ return {
14
+ "http.request.method": input.methodUsed ?? OTEL_HTTP_METHOD_QUERY,
15
+ "http.request.method_original": OTEL_HTTP_METHOD_QUERY,
16
+ "url.full": input.url,
17
+ ...(input.statusCode != null ? { "http.response.status_code": input.statusCode } : {}),
18
+ "http.query.used_fallback": Boolean(input.usedFallback),
19
+ ...(input.bodyBytes != null ? { "http.request.body.size": input.bodyBytes } : {}),
20
+ "http.query.rfc": "10008",
21
+ };
22
+ }
23
+ /**
24
+ * Wrap a QUERY call with a generic tracer-like API (no hard dep on @opentelemetry/api).
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * await withQuerySpan(tracer, { name: 'QUERY /search', url }, () => queryJson(url, init))
29
+ * ```
30
+ */
31
+ export async function withQuerySpan(tracer, opts, fn) {
32
+ return tracer.startActiveSpan(opts.name, async (span) => {
33
+ const attrs = querySpanAttributes({
34
+ url: opts.url,
35
+ bodyBytes: opts.bodyBytes,
36
+ });
37
+ if (typeof span?.setAttributes === "function")
38
+ span.setAttributes(attrs);
39
+ else if (typeof span?.setAttribute === "function") {
40
+ for (const [k, v] of Object.entries(attrs))
41
+ span.setAttribute(k, v);
42
+ }
43
+ try {
44
+ const out = await fn();
45
+ if (typeof span?.end === "function")
46
+ span.end();
47
+ return out;
48
+ }
49
+ catch (e) {
50
+ if (typeof span?.recordException === "function")
51
+ span.recordException(e);
52
+ if (typeof span?.setStatus === "function") {
53
+ span.setStatus({ code: 2, message: String(e?.message || e) });
54
+ }
55
+ if (typeof span?.end === "function")
56
+ span.end();
57
+ throw e;
58
+ }
59
+ });
60
+ }
61
+ /** Log-line friendly label so log scrapers don't drop QUERY */
62
+ export function queryLogFields(meta) {
63
+ return {
64
+ http_method: meta.methodUsed ?? "QUERY",
65
+ http_method_intended: "QUERY",
66
+ http_status: meta.status,
67
+ query_fallback: meta.usedFallback ?? false,
68
+ };
69
+ }
70
+ //# sourceMappingURL=otel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.js","sourceRoot":"","sources":["../src/otel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAgB,CAAC;AAEvD;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAMnC;IACC,OAAO;QACL,qBAAqB,EAAE,KAAK,CAAC,UAAU,IAAI,sBAAsB;QACjE,8BAA8B,EAAE,sBAAsB;QACtD,UAAU,EAAE,KAAK,CAAC,GAAG;QACrB,GAAG,CAAC,KAAK,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,2BAA2B,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,0BAA0B,EAAE,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC;QACvD,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,wBAAwB,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjF,gBAAgB,EAAE,OAAO;KAC1B,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAwF,EACxF,IAAuD,EACvD,EAAoB;IAEpB,OAAO,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACtD,MAAM,KAAK,GAAG,mBAAmB,CAAC;YAChC,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;QACH,IAAI,OAAO,IAAI,EAAE,aAAa,KAAK,UAAU;YAAE,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;aACpE,IAAI,OAAO,IAAI,EAAE,YAAY,KAAK,UAAU,EAAE,CAAC;YAClD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACtE,CAAC;QACD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,EAAE,EAAE,CAAC;YACvB,IAAI,OAAO,IAAI,EAAE,GAAG,KAAK,UAAU;gBAAE,IAAI,CAAC,GAAG,EAAE,CAAC;YAChD,OAAO,GAAG,CAAC;QACb,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,OAAO,IAAI,EAAE,eAAe,KAAK,UAAU;gBAAE,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;YACzE,IAAI,OAAO,IAAI,EAAE,SAAS,KAAK,UAAU,EAAE,CAAC;gBAC1C,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,CAAE,CAAW,EAAE,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;YAC3E,CAAC;YACD,IAAI,OAAO,IAAI,EAAE,GAAG,KAAK,UAAU;gBAAE,IAAI,CAAC,GAAG,EAAE,CAAC;YAChD,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,cAAc,CAAC,IAI9B;IACC,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,UAAU,IAAI,OAAO;QACvC,oBAAoB,EAAE,OAAO;QAC7B,WAAW,EAAE,IAAI,CAAC,MAAM;QACxB,cAAc,EAAE,IAAI,CAAC,YAAY,IAAI,KAAK;KAC3C,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@httpquery/edge",
3
+ "version": "0.1.0",
4
+ "description": "CDN / Cloudflare Workers helpers for HTTP QUERY (RFC 10008) — cache keys that include body.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./cloudflare": {
14
+ "types": "./dist/cloudflare.d.ts",
15
+ "import": "./dist/cloudflare.js"
16
+ },
17
+ "./akamai": {
18
+ "types": "./dist/akamai.d.ts",
19
+ "import": "./dist/akamai.js"
20
+ },
21
+ "./otel": {
22
+ "types": "./dist/otel.d.ts",
23
+ "import": "./dist/otel.js"
24
+ },
25
+ "./auto-cache": {
26
+ "types": "./dist/auto-cache.d.ts",
27
+ "import": "./dist/auto-cache.js"
28
+ },
29
+ "./cf-body-cache": {
30
+ "types": "./dist/cf-body-cache.d.ts",
31
+ "import": "./dist/cf-body-cache.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc -p tsconfig.json",
39
+ "typecheck": "tsc -p tsconfig.json --noEmit",
40
+ "test": "npm run build && node --experimental-strip-types --test src/*.test.ts"
41
+ },
42
+ "dependencies": {
43
+ "@httpquery/core": "0.1.0"
44
+ },
45
+ "keywords": [
46
+ "http",
47
+ "query",
48
+ "rfc10008",
49
+ "cloudflare",
50
+ "workers",
51
+ "cdn",
52
+ "akamai",
53
+ "cache"
54
+ ],
55
+ "license": "MIT",
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "git+https://github.com/http-query/http-query.git",
62
+ "directory": "packages/edge"
63
+ },
64
+ "bugs": {
65
+ "url": "https://github.com/http-query/http-query/issues"
66
+ },
67
+ "homepage": "https://http-query.com",
68
+ "engines": {
69
+ "node": ">=18"
70
+ },
71
+ "sideEffects": false
72
+ }