@kiwa-test/edge 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.cjs +134 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +106 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cardene777
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createKvNamespace: () => createKvNamespace,
|
|
24
|
+
invokeEdgeHandler: () => invokeEdgeHandler
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/invoke-edge-handler.ts
|
|
29
|
+
function buildRequest(opts) {
|
|
30
|
+
const headers = new Headers();
|
|
31
|
+
for (const [name, value] of Object.entries(opts.headers ?? {})) {
|
|
32
|
+
headers.set(name, value);
|
|
33
|
+
}
|
|
34
|
+
let body = null;
|
|
35
|
+
if (typeof opts.formData !== "undefined") {
|
|
36
|
+
const fd = new FormData();
|
|
37
|
+
for (const [name, value] of Object.entries(opts.formData)) {
|
|
38
|
+
fd.set(name, value);
|
|
39
|
+
}
|
|
40
|
+
body = fd;
|
|
41
|
+
} else if (typeof opts.jsonBody !== "undefined") {
|
|
42
|
+
body = JSON.stringify(opts.jsonBody);
|
|
43
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
44
|
+
}
|
|
45
|
+
const method = opts.method ?? (body === null ? "GET" : "POST");
|
|
46
|
+
return new Request(opts.url, body === null ? { method, headers } : { method, headers, body });
|
|
47
|
+
}
|
|
48
|
+
function createCtx() {
|
|
49
|
+
const waitedPromises = [];
|
|
50
|
+
const ctx = {
|
|
51
|
+
waitedPromises,
|
|
52
|
+
passThroughCalled: false,
|
|
53
|
+
waitUntil(promise) {
|
|
54
|
+
waitedPromises.push(promise);
|
|
55
|
+
},
|
|
56
|
+
passThroughOnException() {
|
|
57
|
+
ctx.passThroughCalled = true;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
return ctx;
|
|
61
|
+
}
|
|
62
|
+
async function invokeEdgeHandler(opts) {
|
|
63
|
+
const reqOpts = {
|
|
64
|
+
url: opts.url,
|
|
65
|
+
...typeof opts.method !== "undefined" ? { method: opts.method } : {},
|
|
66
|
+
...typeof opts.headers !== "undefined" ? { headers: opts.headers } : {},
|
|
67
|
+
...typeof opts.formData !== "undefined" ? { formData: opts.formData } : {},
|
|
68
|
+
...typeof opts.jsonBody !== "undefined" ? { jsonBody: opts.jsonBody } : {}
|
|
69
|
+
};
|
|
70
|
+
const request = buildRequest(reqOpts);
|
|
71
|
+
const ctx = createCtx();
|
|
72
|
+
let response;
|
|
73
|
+
let redirect = null;
|
|
74
|
+
let error;
|
|
75
|
+
try {
|
|
76
|
+
response = await opts.handler(request, opts.env, ctx);
|
|
77
|
+
if (response.status >= 300 && response.status < 400) {
|
|
78
|
+
redirect = {
|
|
79
|
+
url: response.headers.get("location") ?? "",
|
|
80
|
+
status: response.status
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
} catch (caught) {
|
|
84
|
+
response = new Response(null, { status: 500 });
|
|
85
|
+
error = caught;
|
|
86
|
+
}
|
|
87
|
+
return { response, redirect, ctx, error };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/kv-mock.ts
|
|
91
|
+
function createKvNamespace(initial = {}) {
|
|
92
|
+
const store = /* @__PURE__ */ new Map();
|
|
93
|
+
for (const [key, value] of Object.entries(initial)) {
|
|
94
|
+
store.set(key, { value });
|
|
95
|
+
}
|
|
96
|
+
async function getImpl(key, type) {
|
|
97
|
+
const entry = store.get(key);
|
|
98
|
+
if (typeof entry === "undefined") return null;
|
|
99
|
+
if (type === "json") return JSON.parse(entry.value);
|
|
100
|
+
if (type === "arrayBuffer") {
|
|
101
|
+
const enc = new TextEncoder().encode(entry.value);
|
|
102
|
+
return enc.buffer.slice(enc.byteOffset, enc.byteOffset + enc.byteLength);
|
|
103
|
+
}
|
|
104
|
+
return entry.value;
|
|
105
|
+
}
|
|
106
|
+
const get = getImpl;
|
|
107
|
+
return {
|
|
108
|
+
get,
|
|
109
|
+
async put(key, value, options) {
|
|
110
|
+
const metadata = options?.metadata;
|
|
111
|
+
store.set(key, typeof metadata === "undefined" ? { value } : { value, metadata });
|
|
112
|
+
},
|
|
113
|
+
async delete(key) {
|
|
114
|
+
store.delete(key);
|
|
115
|
+
},
|
|
116
|
+
async list(options) {
|
|
117
|
+
const prefix = options?.prefix ?? "";
|
|
118
|
+
const limit = options?.limit ?? 1e3;
|
|
119
|
+
const keys = [];
|
|
120
|
+
for (const [name, entry] of store.entries()) {
|
|
121
|
+
if (!name.startsWith(prefix)) continue;
|
|
122
|
+
if (keys.length >= limit) break;
|
|
123
|
+
keys.push(typeof entry.metadata === "undefined" ? { name } : { name, metadata: entry.metadata });
|
|
124
|
+
}
|
|
125
|
+
return { keys, list_complete: true };
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
130
|
+
0 && (module.exports = {
|
|
131
|
+
createKvNamespace,
|
|
132
|
+
invokeEdgeHandler
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/invoke-edge-handler.ts","../src/kv-mock.ts"],"sourcesContent":["export {\n invokeEdgeHandler,\n type EdgeFetchHandler,\n type EdgeEnvBindings,\n type InvokeEdgeHandlerOptions,\n type InvokeEdgeHandlerResult,\n type SimulatedExecutionContext,\n} from './invoke-edge-handler.js';\n\nexport {\n createKvNamespace,\n type KVNamespace,\n type KVNamespacePutOptions,\n type KVNamespaceListOptions,\n type KVNamespaceListResult,\n type KVMockEntry,\n} from './kv-mock.js';\n","// Edge runtime fetch handler test helper for kiwa (Issue #522).\n//\n// Cloudflare Workers / Vercel Edge / generic ESM-style handlers expose a\n// `fetch(request, env, ctx)` entry point. kiwa provides a simulated env\n// (binding bag: KV namespaces, R2 buckets, D1 databases, vars) + an\n// ExecutionContext stub that captures `waitUntil` / `passThroughOnException`\n// calls so tests can assert on background promises without real Workers\n// scheduler.\n\nimport type { KVNamespace } from './kv-mock.js';\n\nexport interface SimulatedExecutionContext {\n waitUntil(promise: Promise<unknown>): void;\n passThroughOnException(): void;\n readonly waitedPromises: Promise<unknown>[];\n passThroughCalled: boolean;\n}\n\nexport interface EdgeEnvBindings {\n readonly [bindingName: string]: KVNamespace | Record<string, unknown> | string | undefined;\n}\n\nexport type EdgeFetchHandler<TEnv extends EdgeEnvBindings = EdgeEnvBindings> = (\n request: Request,\n env: TEnv,\n ctx: SimulatedExecutionContext,\n) => Promise<Response> | Response;\n\nexport interface InvokeEdgeHandlerOptions<TEnv extends EdgeEnvBindings = EdgeEnvBindings> {\n readonly handler: EdgeFetchHandler<TEnv>;\n readonly url: string;\n readonly method?: string;\n readonly headers?: Record<string, string>;\n readonly formData?: Record<string, string>;\n readonly jsonBody?: unknown;\n readonly env: TEnv;\n}\n\nexport interface InvokeEdgeHandlerResult {\n readonly response: Response;\n readonly redirect: { url: string; status: number } | null;\n readonly ctx: SimulatedExecutionContext;\n readonly error: unknown;\n}\n\nfunction buildRequest(opts: {\n readonly url: string;\n readonly method?: string;\n readonly headers?: Record<string, string>;\n readonly formData?: Record<string, string>;\n readonly jsonBody?: unknown;\n}): Request {\n const headers = new Headers();\n for (const [name, value] of Object.entries(opts.headers ?? {})) {\n headers.set(name, value);\n }\n let body: BodyInit | null = null;\n if (typeof opts.formData !== 'undefined') {\n const fd = new FormData();\n for (const [name, value] of Object.entries(opts.formData)) {\n fd.set(name, value);\n }\n body = fd;\n } else if (typeof opts.jsonBody !== 'undefined') {\n body = JSON.stringify(opts.jsonBody);\n if (!headers.has('content-type')) headers.set('content-type', 'application/json');\n }\n const method = opts.method ?? (body === null ? 'GET' : 'POST');\n return new Request(opts.url, body === null ? { method, headers } : { method, headers, body });\n}\n\nfunction createCtx(): SimulatedExecutionContext {\n const waitedPromises: Promise<unknown>[] = [];\n const ctx: SimulatedExecutionContext = {\n waitedPromises,\n passThroughCalled: false,\n waitUntil(promise) {\n waitedPromises.push(promise);\n },\n passThroughOnException() {\n ctx.passThroughCalled = true;\n },\n };\n return ctx;\n}\n\n/**\n * Invoke an edge runtime fetch handler in isolation and capture the returned\n * Response + ExecutionContext side effects. The caller supplies `env` so KV /\n * R2 / vars stay explicit in each test (no global state).\n */\nexport async function invokeEdgeHandler<TEnv extends EdgeEnvBindings = EdgeEnvBindings>(\n opts: InvokeEdgeHandlerOptions<TEnv>,\n): Promise<InvokeEdgeHandlerResult> {\n const reqOpts: {\n readonly url: string;\n readonly method?: string;\n readonly headers?: Record<string, string>;\n readonly formData?: Record<string, string>;\n readonly jsonBody?: unknown;\n } = {\n url: opts.url,\n ...(typeof opts.method !== 'undefined' ? { method: opts.method } : {}),\n ...(typeof opts.headers !== 'undefined' ? { headers: opts.headers } : {}),\n ...(typeof opts.formData !== 'undefined' ? { formData: opts.formData } : {}),\n ...(typeof opts.jsonBody !== 'undefined' ? { jsonBody: opts.jsonBody } : {}),\n };\n const request = buildRequest(reqOpts);\n const ctx = createCtx();\n let response: Response;\n let redirect: { url: string; status: number } | null = null;\n let error: unknown;\n try {\n response = await opts.handler(request, opts.env, ctx);\n if (response.status >= 300 && response.status < 400) {\n redirect = {\n url: response.headers.get('location') ?? '',\n status: response.status,\n };\n }\n } catch (caught) {\n response = new Response(null, { status: 500 });\n error = caught;\n }\n return { response, redirect, ctx, error };\n}\n","// Minimal Cloudflare KV namespace mock for kiwa edge tests.\n//\n// Implements the subset that production Workers code touches in practice:\n// - `get(key)` / `get(key, { type: 'json' | 'text' | 'arrayBuffer' })`\n// - `put(key, value, options?)` (TTL captured but ignored)\n// - `delete(key)`\n// - `list({ prefix?, limit? })` (no cursor pagination)\n//\n// Real Workers KV is eventually consistent and has a 1KB/key/24h propagation;\n// kiwa's mock is strongly consistent because tests need determinism.\n\nexport interface KVNamespacePutOptions {\n readonly expirationTtl?: number;\n readonly metadata?: Record<string, unknown>;\n}\n\nexport interface KVNamespaceListOptions {\n readonly prefix?: string;\n readonly limit?: number;\n}\n\nexport interface KVNamespaceListResult {\n readonly keys: ReadonlyArray<{ readonly name: string; readonly metadata?: Record<string, unknown> }>;\n readonly list_complete: true;\n}\n\nexport interface KVNamespace {\n get(key: string): Promise<string | null>;\n get(key: string, type: 'text'): Promise<string | null>;\n get<T>(key: string, type: 'json'): Promise<T | null>;\n get(key: string, type: 'arrayBuffer'): Promise<ArrayBuffer | null>;\n put(key: string, value: string, options?: KVNamespacePutOptions): Promise<void>;\n delete(key: string): Promise<void>;\n list(options?: KVNamespaceListOptions): Promise<KVNamespaceListResult>;\n}\n\nexport interface KVMockEntry {\n readonly value: string;\n readonly metadata?: Record<string, unknown>;\n}\n\nexport function createKvNamespace(initial: Record<string, string> = {}): KVNamespace {\n const store = new Map<string, KVMockEntry>();\n for (const [key, value] of Object.entries(initial)) {\n store.set(key, { value });\n }\n async function getImpl(key: string, type?: 'text' | 'json' | 'arrayBuffer'): Promise<unknown> {\n const entry = store.get(key);\n if (typeof entry === 'undefined') return null;\n if (type === 'json') return JSON.parse(entry.value) as unknown;\n if (type === 'arrayBuffer') {\n const enc = new TextEncoder().encode(entry.value);\n return enc.buffer.slice(enc.byteOffset, enc.byteOffset + enc.byteLength) as ArrayBuffer;\n }\n return entry.value;\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const get = getImpl as any;\n return {\n get,\n async put(key: string, value: string, options?: KVNamespacePutOptions): Promise<void> {\n const metadata = options?.metadata;\n store.set(key, typeof metadata === 'undefined' ? { value } : { value, metadata });\n },\n async delete(key: string): Promise<void> {\n store.delete(key);\n },\n async list(options?: KVNamespaceListOptions): Promise<KVNamespaceListResult> {\n const prefix = options?.prefix ?? '';\n const limit = options?.limit ?? 1000;\n const keys: Array<{ name: string; metadata?: Record<string, unknown> }> = [];\n for (const [name, entry] of store.entries()) {\n if (!name.startsWith(prefix)) continue;\n if (keys.length >= limit) break;\n keys.push(typeof entry.metadata === 'undefined' ? { name } : { name, metadata: entry.metadata });\n }\n return { keys, list_complete: true };\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
interface KVNamespacePutOptions {
|
|
2
|
+
readonly expirationTtl?: number;
|
|
3
|
+
readonly metadata?: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
interface KVNamespaceListOptions {
|
|
6
|
+
readonly prefix?: string;
|
|
7
|
+
readonly limit?: number;
|
|
8
|
+
}
|
|
9
|
+
interface KVNamespaceListResult {
|
|
10
|
+
readonly keys: ReadonlyArray<{
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly metadata?: Record<string, unknown>;
|
|
13
|
+
}>;
|
|
14
|
+
readonly list_complete: true;
|
|
15
|
+
}
|
|
16
|
+
interface KVNamespace {
|
|
17
|
+
get(key: string): Promise<string | null>;
|
|
18
|
+
get(key: string, type: 'text'): Promise<string | null>;
|
|
19
|
+
get<T>(key: string, type: 'json'): Promise<T | null>;
|
|
20
|
+
get(key: string, type: 'arrayBuffer'): Promise<ArrayBuffer | null>;
|
|
21
|
+
put(key: string, value: string, options?: KVNamespacePutOptions): Promise<void>;
|
|
22
|
+
delete(key: string): Promise<void>;
|
|
23
|
+
list(options?: KVNamespaceListOptions): Promise<KVNamespaceListResult>;
|
|
24
|
+
}
|
|
25
|
+
interface KVMockEntry {
|
|
26
|
+
readonly value: string;
|
|
27
|
+
readonly metadata?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
declare function createKvNamespace(initial?: Record<string, string>): KVNamespace;
|
|
30
|
+
|
|
31
|
+
interface SimulatedExecutionContext {
|
|
32
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
33
|
+
passThroughOnException(): void;
|
|
34
|
+
readonly waitedPromises: Promise<unknown>[];
|
|
35
|
+
passThroughCalled: boolean;
|
|
36
|
+
}
|
|
37
|
+
interface EdgeEnvBindings {
|
|
38
|
+
readonly [bindingName: string]: KVNamespace | Record<string, unknown> | string | undefined;
|
|
39
|
+
}
|
|
40
|
+
type EdgeFetchHandler<TEnv extends EdgeEnvBindings = EdgeEnvBindings> = (request: Request, env: TEnv, ctx: SimulatedExecutionContext) => Promise<Response> | Response;
|
|
41
|
+
interface InvokeEdgeHandlerOptions<TEnv extends EdgeEnvBindings = EdgeEnvBindings> {
|
|
42
|
+
readonly handler: EdgeFetchHandler<TEnv>;
|
|
43
|
+
readonly url: string;
|
|
44
|
+
readonly method?: string;
|
|
45
|
+
readonly headers?: Record<string, string>;
|
|
46
|
+
readonly formData?: Record<string, string>;
|
|
47
|
+
readonly jsonBody?: unknown;
|
|
48
|
+
readonly env: TEnv;
|
|
49
|
+
}
|
|
50
|
+
interface InvokeEdgeHandlerResult {
|
|
51
|
+
readonly response: Response;
|
|
52
|
+
readonly redirect: {
|
|
53
|
+
url: string;
|
|
54
|
+
status: number;
|
|
55
|
+
} | null;
|
|
56
|
+
readonly ctx: SimulatedExecutionContext;
|
|
57
|
+
readonly error: unknown;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Invoke an edge runtime fetch handler in isolation and capture the returned
|
|
61
|
+
* Response + ExecutionContext side effects. The caller supplies `env` so KV /
|
|
62
|
+
* R2 / vars stay explicit in each test (no global state).
|
|
63
|
+
*/
|
|
64
|
+
declare function invokeEdgeHandler<TEnv extends EdgeEnvBindings = EdgeEnvBindings>(opts: InvokeEdgeHandlerOptions<TEnv>): Promise<InvokeEdgeHandlerResult>;
|
|
65
|
+
|
|
66
|
+
export { type EdgeEnvBindings, type EdgeFetchHandler, type InvokeEdgeHandlerOptions, type InvokeEdgeHandlerResult, type KVMockEntry, type KVNamespace, type KVNamespaceListOptions, type KVNamespaceListResult, type KVNamespacePutOptions, type SimulatedExecutionContext, createKvNamespace, invokeEdgeHandler };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
interface KVNamespacePutOptions {
|
|
2
|
+
readonly expirationTtl?: number;
|
|
3
|
+
readonly metadata?: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
interface KVNamespaceListOptions {
|
|
6
|
+
readonly prefix?: string;
|
|
7
|
+
readonly limit?: number;
|
|
8
|
+
}
|
|
9
|
+
interface KVNamespaceListResult {
|
|
10
|
+
readonly keys: ReadonlyArray<{
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly metadata?: Record<string, unknown>;
|
|
13
|
+
}>;
|
|
14
|
+
readonly list_complete: true;
|
|
15
|
+
}
|
|
16
|
+
interface KVNamespace {
|
|
17
|
+
get(key: string): Promise<string | null>;
|
|
18
|
+
get(key: string, type: 'text'): Promise<string | null>;
|
|
19
|
+
get<T>(key: string, type: 'json'): Promise<T | null>;
|
|
20
|
+
get(key: string, type: 'arrayBuffer'): Promise<ArrayBuffer | null>;
|
|
21
|
+
put(key: string, value: string, options?: KVNamespacePutOptions): Promise<void>;
|
|
22
|
+
delete(key: string): Promise<void>;
|
|
23
|
+
list(options?: KVNamespaceListOptions): Promise<KVNamespaceListResult>;
|
|
24
|
+
}
|
|
25
|
+
interface KVMockEntry {
|
|
26
|
+
readonly value: string;
|
|
27
|
+
readonly metadata?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
declare function createKvNamespace(initial?: Record<string, string>): KVNamespace;
|
|
30
|
+
|
|
31
|
+
interface SimulatedExecutionContext {
|
|
32
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
33
|
+
passThroughOnException(): void;
|
|
34
|
+
readonly waitedPromises: Promise<unknown>[];
|
|
35
|
+
passThroughCalled: boolean;
|
|
36
|
+
}
|
|
37
|
+
interface EdgeEnvBindings {
|
|
38
|
+
readonly [bindingName: string]: KVNamespace | Record<string, unknown> | string | undefined;
|
|
39
|
+
}
|
|
40
|
+
type EdgeFetchHandler<TEnv extends EdgeEnvBindings = EdgeEnvBindings> = (request: Request, env: TEnv, ctx: SimulatedExecutionContext) => Promise<Response> | Response;
|
|
41
|
+
interface InvokeEdgeHandlerOptions<TEnv extends EdgeEnvBindings = EdgeEnvBindings> {
|
|
42
|
+
readonly handler: EdgeFetchHandler<TEnv>;
|
|
43
|
+
readonly url: string;
|
|
44
|
+
readonly method?: string;
|
|
45
|
+
readonly headers?: Record<string, string>;
|
|
46
|
+
readonly formData?: Record<string, string>;
|
|
47
|
+
readonly jsonBody?: unknown;
|
|
48
|
+
readonly env: TEnv;
|
|
49
|
+
}
|
|
50
|
+
interface InvokeEdgeHandlerResult {
|
|
51
|
+
readonly response: Response;
|
|
52
|
+
readonly redirect: {
|
|
53
|
+
url: string;
|
|
54
|
+
status: number;
|
|
55
|
+
} | null;
|
|
56
|
+
readonly ctx: SimulatedExecutionContext;
|
|
57
|
+
readonly error: unknown;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Invoke an edge runtime fetch handler in isolation and capture the returned
|
|
61
|
+
* Response + ExecutionContext side effects. The caller supplies `env` so KV /
|
|
62
|
+
* R2 / vars stay explicit in each test (no global state).
|
|
63
|
+
*/
|
|
64
|
+
declare function invokeEdgeHandler<TEnv extends EdgeEnvBindings = EdgeEnvBindings>(opts: InvokeEdgeHandlerOptions<TEnv>): Promise<InvokeEdgeHandlerResult>;
|
|
65
|
+
|
|
66
|
+
export { type EdgeEnvBindings, type EdgeFetchHandler, type InvokeEdgeHandlerOptions, type InvokeEdgeHandlerResult, type KVMockEntry, type KVNamespace, type KVNamespaceListOptions, type KVNamespaceListResult, type KVNamespacePutOptions, type SimulatedExecutionContext, createKvNamespace, invokeEdgeHandler };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// src/invoke-edge-handler.ts
|
|
2
|
+
function buildRequest(opts) {
|
|
3
|
+
const headers = new Headers();
|
|
4
|
+
for (const [name, value] of Object.entries(opts.headers ?? {})) {
|
|
5
|
+
headers.set(name, value);
|
|
6
|
+
}
|
|
7
|
+
let body = null;
|
|
8
|
+
if (typeof opts.formData !== "undefined") {
|
|
9
|
+
const fd = new FormData();
|
|
10
|
+
for (const [name, value] of Object.entries(opts.formData)) {
|
|
11
|
+
fd.set(name, value);
|
|
12
|
+
}
|
|
13
|
+
body = fd;
|
|
14
|
+
} else if (typeof opts.jsonBody !== "undefined") {
|
|
15
|
+
body = JSON.stringify(opts.jsonBody);
|
|
16
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
17
|
+
}
|
|
18
|
+
const method = opts.method ?? (body === null ? "GET" : "POST");
|
|
19
|
+
return new Request(opts.url, body === null ? { method, headers } : { method, headers, body });
|
|
20
|
+
}
|
|
21
|
+
function createCtx() {
|
|
22
|
+
const waitedPromises = [];
|
|
23
|
+
const ctx = {
|
|
24
|
+
waitedPromises,
|
|
25
|
+
passThroughCalled: false,
|
|
26
|
+
waitUntil(promise) {
|
|
27
|
+
waitedPromises.push(promise);
|
|
28
|
+
},
|
|
29
|
+
passThroughOnException() {
|
|
30
|
+
ctx.passThroughCalled = true;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
return ctx;
|
|
34
|
+
}
|
|
35
|
+
async function invokeEdgeHandler(opts) {
|
|
36
|
+
const reqOpts = {
|
|
37
|
+
url: opts.url,
|
|
38
|
+
...typeof opts.method !== "undefined" ? { method: opts.method } : {},
|
|
39
|
+
...typeof opts.headers !== "undefined" ? { headers: opts.headers } : {},
|
|
40
|
+
...typeof opts.formData !== "undefined" ? { formData: opts.formData } : {},
|
|
41
|
+
...typeof opts.jsonBody !== "undefined" ? { jsonBody: opts.jsonBody } : {}
|
|
42
|
+
};
|
|
43
|
+
const request = buildRequest(reqOpts);
|
|
44
|
+
const ctx = createCtx();
|
|
45
|
+
let response;
|
|
46
|
+
let redirect = null;
|
|
47
|
+
let error;
|
|
48
|
+
try {
|
|
49
|
+
response = await opts.handler(request, opts.env, ctx);
|
|
50
|
+
if (response.status >= 300 && response.status < 400) {
|
|
51
|
+
redirect = {
|
|
52
|
+
url: response.headers.get("location") ?? "",
|
|
53
|
+
status: response.status
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
} catch (caught) {
|
|
57
|
+
response = new Response(null, { status: 500 });
|
|
58
|
+
error = caught;
|
|
59
|
+
}
|
|
60
|
+
return { response, redirect, ctx, error };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/kv-mock.ts
|
|
64
|
+
function createKvNamespace(initial = {}) {
|
|
65
|
+
const store = /* @__PURE__ */ new Map();
|
|
66
|
+
for (const [key, value] of Object.entries(initial)) {
|
|
67
|
+
store.set(key, { value });
|
|
68
|
+
}
|
|
69
|
+
async function getImpl(key, type) {
|
|
70
|
+
const entry = store.get(key);
|
|
71
|
+
if (typeof entry === "undefined") return null;
|
|
72
|
+
if (type === "json") return JSON.parse(entry.value);
|
|
73
|
+
if (type === "arrayBuffer") {
|
|
74
|
+
const enc = new TextEncoder().encode(entry.value);
|
|
75
|
+
return enc.buffer.slice(enc.byteOffset, enc.byteOffset + enc.byteLength);
|
|
76
|
+
}
|
|
77
|
+
return entry.value;
|
|
78
|
+
}
|
|
79
|
+
const get = getImpl;
|
|
80
|
+
return {
|
|
81
|
+
get,
|
|
82
|
+
async put(key, value, options) {
|
|
83
|
+
const metadata = options?.metadata;
|
|
84
|
+
store.set(key, typeof metadata === "undefined" ? { value } : { value, metadata });
|
|
85
|
+
},
|
|
86
|
+
async delete(key) {
|
|
87
|
+
store.delete(key);
|
|
88
|
+
},
|
|
89
|
+
async list(options) {
|
|
90
|
+
const prefix = options?.prefix ?? "";
|
|
91
|
+
const limit = options?.limit ?? 1e3;
|
|
92
|
+
const keys = [];
|
|
93
|
+
for (const [name, entry] of store.entries()) {
|
|
94
|
+
if (!name.startsWith(prefix)) continue;
|
|
95
|
+
if (keys.length >= limit) break;
|
|
96
|
+
keys.push(typeof entry.metadata === "undefined" ? { name } : { name, metadata: entry.metadata });
|
|
97
|
+
}
|
|
98
|
+
return { keys, list_complete: true };
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
createKvNamespace,
|
|
104
|
+
invokeEdgeHandler
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/invoke-edge-handler.ts","../src/kv-mock.ts"],"sourcesContent":["// Edge runtime fetch handler test helper for kiwa (Issue #522).\n//\n// Cloudflare Workers / Vercel Edge / generic ESM-style handlers expose a\n// `fetch(request, env, ctx)` entry point. kiwa provides a simulated env\n// (binding bag: KV namespaces, R2 buckets, D1 databases, vars) + an\n// ExecutionContext stub that captures `waitUntil` / `passThroughOnException`\n// calls so tests can assert on background promises without real Workers\n// scheduler.\n\nimport type { KVNamespace } from './kv-mock.js';\n\nexport interface SimulatedExecutionContext {\n waitUntil(promise: Promise<unknown>): void;\n passThroughOnException(): void;\n readonly waitedPromises: Promise<unknown>[];\n passThroughCalled: boolean;\n}\n\nexport interface EdgeEnvBindings {\n readonly [bindingName: string]: KVNamespace | Record<string, unknown> | string | undefined;\n}\n\nexport type EdgeFetchHandler<TEnv extends EdgeEnvBindings = EdgeEnvBindings> = (\n request: Request,\n env: TEnv,\n ctx: SimulatedExecutionContext,\n) => Promise<Response> | Response;\n\nexport interface InvokeEdgeHandlerOptions<TEnv extends EdgeEnvBindings = EdgeEnvBindings> {\n readonly handler: EdgeFetchHandler<TEnv>;\n readonly url: string;\n readonly method?: string;\n readonly headers?: Record<string, string>;\n readonly formData?: Record<string, string>;\n readonly jsonBody?: unknown;\n readonly env: TEnv;\n}\n\nexport interface InvokeEdgeHandlerResult {\n readonly response: Response;\n readonly redirect: { url: string; status: number } | null;\n readonly ctx: SimulatedExecutionContext;\n readonly error: unknown;\n}\n\nfunction buildRequest(opts: {\n readonly url: string;\n readonly method?: string;\n readonly headers?: Record<string, string>;\n readonly formData?: Record<string, string>;\n readonly jsonBody?: unknown;\n}): Request {\n const headers = new Headers();\n for (const [name, value] of Object.entries(opts.headers ?? {})) {\n headers.set(name, value);\n }\n let body: BodyInit | null = null;\n if (typeof opts.formData !== 'undefined') {\n const fd = new FormData();\n for (const [name, value] of Object.entries(opts.formData)) {\n fd.set(name, value);\n }\n body = fd;\n } else if (typeof opts.jsonBody !== 'undefined') {\n body = JSON.stringify(opts.jsonBody);\n if (!headers.has('content-type')) headers.set('content-type', 'application/json');\n }\n const method = opts.method ?? (body === null ? 'GET' : 'POST');\n return new Request(opts.url, body === null ? { method, headers } : { method, headers, body });\n}\n\nfunction createCtx(): SimulatedExecutionContext {\n const waitedPromises: Promise<unknown>[] = [];\n const ctx: SimulatedExecutionContext = {\n waitedPromises,\n passThroughCalled: false,\n waitUntil(promise) {\n waitedPromises.push(promise);\n },\n passThroughOnException() {\n ctx.passThroughCalled = true;\n },\n };\n return ctx;\n}\n\n/**\n * Invoke an edge runtime fetch handler in isolation and capture the returned\n * Response + ExecutionContext side effects. The caller supplies `env` so KV /\n * R2 / vars stay explicit in each test (no global state).\n */\nexport async function invokeEdgeHandler<TEnv extends EdgeEnvBindings = EdgeEnvBindings>(\n opts: InvokeEdgeHandlerOptions<TEnv>,\n): Promise<InvokeEdgeHandlerResult> {\n const reqOpts: {\n readonly url: string;\n readonly method?: string;\n readonly headers?: Record<string, string>;\n readonly formData?: Record<string, string>;\n readonly jsonBody?: unknown;\n } = {\n url: opts.url,\n ...(typeof opts.method !== 'undefined' ? { method: opts.method } : {}),\n ...(typeof opts.headers !== 'undefined' ? { headers: opts.headers } : {}),\n ...(typeof opts.formData !== 'undefined' ? { formData: opts.formData } : {}),\n ...(typeof opts.jsonBody !== 'undefined' ? { jsonBody: opts.jsonBody } : {}),\n };\n const request = buildRequest(reqOpts);\n const ctx = createCtx();\n let response: Response;\n let redirect: { url: string; status: number } | null = null;\n let error: unknown;\n try {\n response = await opts.handler(request, opts.env, ctx);\n if (response.status >= 300 && response.status < 400) {\n redirect = {\n url: response.headers.get('location') ?? '',\n status: response.status,\n };\n }\n } catch (caught) {\n response = new Response(null, { status: 500 });\n error = caught;\n }\n return { response, redirect, ctx, error };\n}\n","// Minimal Cloudflare KV namespace mock for kiwa edge tests.\n//\n// Implements the subset that production Workers code touches in practice:\n// - `get(key)` / `get(key, { type: 'json' | 'text' | 'arrayBuffer' })`\n// - `put(key, value, options?)` (TTL captured but ignored)\n// - `delete(key)`\n// - `list({ prefix?, limit? })` (no cursor pagination)\n//\n// Real Workers KV is eventually consistent and has a 1KB/key/24h propagation;\n// kiwa's mock is strongly consistent because tests need determinism.\n\nexport interface KVNamespacePutOptions {\n readonly expirationTtl?: number;\n readonly metadata?: Record<string, unknown>;\n}\n\nexport interface KVNamespaceListOptions {\n readonly prefix?: string;\n readonly limit?: number;\n}\n\nexport interface KVNamespaceListResult {\n readonly keys: ReadonlyArray<{ readonly name: string; readonly metadata?: Record<string, unknown> }>;\n readonly list_complete: true;\n}\n\nexport interface KVNamespace {\n get(key: string): Promise<string | null>;\n get(key: string, type: 'text'): Promise<string | null>;\n get<T>(key: string, type: 'json'): Promise<T | null>;\n get(key: string, type: 'arrayBuffer'): Promise<ArrayBuffer | null>;\n put(key: string, value: string, options?: KVNamespacePutOptions): Promise<void>;\n delete(key: string): Promise<void>;\n list(options?: KVNamespaceListOptions): Promise<KVNamespaceListResult>;\n}\n\nexport interface KVMockEntry {\n readonly value: string;\n readonly metadata?: Record<string, unknown>;\n}\n\nexport function createKvNamespace(initial: Record<string, string> = {}): KVNamespace {\n const store = new Map<string, KVMockEntry>();\n for (const [key, value] of Object.entries(initial)) {\n store.set(key, { value });\n }\n async function getImpl(key: string, type?: 'text' | 'json' | 'arrayBuffer'): Promise<unknown> {\n const entry = store.get(key);\n if (typeof entry === 'undefined') return null;\n if (type === 'json') return JSON.parse(entry.value) as unknown;\n if (type === 'arrayBuffer') {\n const enc = new TextEncoder().encode(entry.value);\n return enc.buffer.slice(enc.byteOffset, enc.byteOffset + enc.byteLength) as ArrayBuffer;\n }\n return entry.value;\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const get = getImpl as any;\n return {\n get,\n async put(key: string, value: string, options?: KVNamespacePutOptions): Promise<void> {\n const metadata = options?.metadata;\n store.set(key, typeof metadata === 'undefined' ? { value } : { value, metadata });\n },\n async delete(key: string): Promise<void> {\n store.delete(key);\n },\n async list(options?: KVNamespaceListOptions): Promise<KVNamespaceListResult> {\n const prefix = options?.prefix ?? '';\n const limit = options?.limit ?? 1000;\n const keys: Array<{ name: string; metadata?: Record<string, unknown> }> = [];\n for (const [name, entry] of store.entries()) {\n if (!name.startsWith(prefix)) continue;\n if (keys.length >= limit) break;\n keys.push(typeof entry.metadata === 'undefined' ? { name } : { name, metadata: entry.metadata });\n }\n return { keys, list_complete: true };\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kiwa-test/edge",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Edge runtime fetch handler test adapter for kiwa — invoke Cloudflare Workers / Vercel Edge / generic edge handlers with simulated env (KV / R2 / D1 / DurableObject minimal mocks) + ExecutionContext (waitUntil / passThroughOnException) without a running Miniflare or workerd (part of the kiwa test framework family)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "cardene",
|
|
8
|
+
"url": "https://github.com/cardene777"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"kiwa",
|
|
12
|
+
"edge",
|
|
13
|
+
"cloudflare-workers",
|
|
14
|
+
"vercel-edge",
|
|
15
|
+
"miniflare",
|
|
16
|
+
"kv",
|
|
17
|
+
"r2",
|
|
18
|
+
"d1",
|
|
19
|
+
"testing",
|
|
20
|
+
"vitest"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/cardene777/kiwa.git",
|
|
25
|
+
"directory": "packages/edge"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/cardene777/kiwa#readme",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/cardene777/kiwa/issues"
|
|
30
|
+
},
|
|
31
|
+
"type": "module",
|
|
32
|
+
"main": "./dist/index.cjs",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"import": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"default": "./dist/index.js"
|
|
40
|
+
},
|
|
41
|
+
"require": {
|
|
42
|
+
"types": "./dist/index.d.cts",
|
|
43
|
+
"default": "./dist/index.cjs"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"dist",
|
|
49
|
+
"README.md"
|
|
50
|
+
],
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public",
|
|
53
|
+
"provenance": false
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=20"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@kiwa-test/core": "1.0.1"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"vitest": "^2"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@stryker-mutator/core": "^8.7.1",
|
|
66
|
+
"@stryker-mutator/vitest-runner": "^8.7.1",
|
|
67
|
+
"@types/node": "^22.10.0",
|
|
68
|
+
"@vitest/coverage-v8": "^2.1.0",
|
|
69
|
+
"tsup": "^8.3.0",
|
|
70
|
+
"typescript": "^5.6.0",
|
|
71
|
+
"vitest": "^2.1.0"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"build": "tsup",
|
|
75
|
+
"test": "node -e \"require('node:fs').rmSync('.vitest-dist',{recursive:true,force:true})\" && tsc -p tsconfig.vitest.json && vitest run .vitest-dist/tests --environment node",
|
|
76
|
+
"test:cov": "node -e \"require('node:fs').rmSync('.vitest-dist',{recursive:true,force:true})\" && tsc -p tsconfig.vitest.json && vitest run .vitest-dist/tests --environment node --coverage --coverage.provider=v8 --coverage.reporter=json --coverage.reporter=json-summary --coverage.reporter=text --coverage.reportsDirectory=coverage --coverage.include='.vitest-dist/src/invoke-edge-handler.js' --coverage.include='.vitest-dist/src/kv-mock.js' --coverage.exclude='.vitest-dist/tests/**' --coverage.exclude='.vitest-dist/src/index.js'",
|
|
77
|
+
"test:mutation": "stryker run",
|
|
78
|
+
"typecheck": "tsc --noEmit"
|
|
79
|
+
}
|
|
80
|
+
}
|