two-stroke 1.0.6 → 1.0.7
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/bin/test.mjs +2 -2
- package/package.json +16 -15
- package/src/index.ts +48 -18
- package/src/openAPI.ts +14 -3
- package/src/test.ts +65 -3
- package/src/types.ts +18 -11
- package/tsconfig.json +3 -8
package/bin/test.mjs
CHANGED
|
@@ -9,14 +9,14 @@ if (fs.existsSync("wrangler.toml")) {
|
|
|
9
9
|
await cmd("wrangler deploy --dry-run --outdir=dist");
|
|
10
10
|
const app = await import(`${process.cwd()}/dist/index.js`);
|
|
11
11
|
const request = await app.default.fetch(
|
|
12
|
-
{ url: "http://example.com/doc
|
|
12
|
+
{ url: "http://example.com/doc", method: "GET" },
|
|
13
13
|
{ SENTRY_DSN: null, SENTRY_ENVIRONMENT: null },
|
|
14
14
|
null,
|
|
15
15
|
);
|
|
16
16
|
const types = await openapiTS(await consumers.json(request.body));
|
|
17
17
|
fs.writeFileSync("test/api.d.ts", types);
|
|
18
18
|
}
|
|
19
|
-
await cmd("vitest --globals --no-file-parallelism
|
|
19
|
+
await cmd("vitest --globals --no-file-parallelism", [
|
|
20
20
|
...(!process.argv.slice(2).includes("-w") &&
|
|
21
21
|
!process.argv.slice(2).includes("--watch")
|
|
22
22
|
? ["--run"]
|
package/package.json
CHANGED
|
@@ -9,20 +9,21 @@
|
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anatine/zod-openapi": "^2.2.3",
|
|
12
|
-
"@asteasolutions/zod-to-openapi": "^6.
|
|
13
|
-
"@cloudflare/workers-types": "^4.
|
|
14
|
-
"@sentry/cli": "^2.
|
|
15
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
16
|
-
"@typescript-eslint/parser": "^7.
|
|
17
|
-
"@vitest/coverage-v8": "^1.
|
|
12
|
+
"@asteasolutions/zod-to-openapi": "^6.4.0",
|
|
13
|
+
"@cloudflare/workers-types": "^4.20240222.0",
|
|
14
|
+
"@sentry/cli": "^2.30.0",
|
|
15
|
+
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
|
16
|
+
"@typescript-eslint/parser": "^7.1.1",
|
|
17
|
+
"@vitest/coverage-v8": "^1.3.1",
|
|
18
18
|
"eslint-config-prettier": "^9.1.0",
|
|
19
19
|
"eslint-config-two-stroke": "^1.0.0",
|
|
20
20
|
"eslint-config-typescript": "^3.0.0",
|
|
21
|
+
"jose": "^5.2.3",
|
|
21
22
|
"jwk-subtle": "^1.0.6",
|
|
22
|
-
"miniflare": "^3.
|
|
23
|
-
"openapi-fetch": "^0.9.
|
|
23
|
+
"miniflare": "^3.20240223.1",
|
|
24
|
+
"openapi-fetch": "^0.9.3",
|
|
24
25
|
"openapi-typescript": "^6.7.4",
|
|
25
|
-
"openapi3-ts": "^4.2.
|
|
26
|
+
"openapi3-ts": "^4.2.2",
|
|
26
27
|
"pbkdf-subtle": "^1.0.0",
|
|
27
28
|
"toml": "^3.0.0",
|
|
28
29
|
"toucan-js": "^3.3.1",
|
|
@@ -50,13 +51,13 @@
|
|
|
50
51
|
"name": "two-stroke",
|
|
51
52
|
"packageManager": "yarn@4.1.0",
|
|
52
53
|
"type": "module",
|
|
53
|
-
"version": "1.0.
|
|
54
|
+
"version": "1.0.7",
|
|
54
55
|
"devDependencies": {
|
|
55
|
-
"@types/eslint": "^8.56.
|
|
56
|
-
"@types/node": "^20.11.
|
|
57
|
-
"eslint": "^8.
|
|
56
|
+
"@types/eslint": "^8.56.5",
|
|
57
|
+
"@types/node": "^20.11.25",
|
|
58
|
+
"eslint": "^8.57.0",
|
|
58
59
|
"prettier": "^3.2.5",
|
|
59
|
-
"typescript": "^5.
|
|
60
|
-
"vitest": "^1.
|
|
60
|
+
"typescript": "^5.4.2",
|
|
61
|
+
"vitest": "^1.3.1"
|
|
61
62
|
}
|
|
62
63
|
}
|
package/src/index.ts
CHANGED
|
@@ -16,12 +16,13 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
16
16
|
env: T;
|
|
17
17
|
sentry: Toucan;
|
|
18
18
|
}) => Promise<void>;
|
|
19
|
-
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
const routes: Route<T, any>[] = [];
|
|
20
21
|
routes.push({
|
|
21
22
|
auth: noAuth,
|
|
22
23
|
method: "GET",
|
|
23
|
-
path: "/doc
|
|
24
|
-
matcher: /^\/doc
|
|
24
|
+
path: "/doc",
|
|
25
|
+
matcher: /^\/doc$/,
|
|
25
26
|
output: z.object({}),
|
|
26
27
|
handler: openAPI(title, release, noAuth, routes),
|
|
27
28
|
});
|
|
@@ -53,6 +54,17 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
53
54
|
release,
|
|
54
55
|
});
|
|
55
56
|
try {
|
|
57
|
+
if (req.method == "OPTIONS") {
|
|
58
|
+
return new Response("", {
|
|
59
|
+
status: 204,
|
|
60
|
+
headers: {
|
|
61
|
+
"Access-Control-Allow-Origin": "*",
|
|
62
|
+
"Access-Control-Max-Age": "86400",
|
|
63
|
+
"Access-Control-Allow-Methods": "GET,PUT,POST,DELETE",
|
|
64
|
+
"Access-Control-Allow-Headers": "Authorization,Content-Type",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
56
68
|
const { pathname } = new URL(req.url);
|
|
57
69
|
let response;
|
|
58
70
|
for (const route of routes) {
|
|
@@ -68,6 +80,7 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
68
80
|
statusText: "Invalid Authorization",
|
|
69
81
|
headers: {
|
|
70
82
|
"WWW-Authenticate": "Bearer",
|
|
83
|
+
"Access-Control-Allow-Origin": "*",
|
|
71
84
|
},
|
|
72
85
|
});
|
|
73
86
|
}
|
|
@@ -94,9 +107,18 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
94
107
|
error: body.error,
|
|
95
108
|
body: rawBody,
|
|
96
109
|
});
|
|
97
|
-
return new Response(
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
return new Response(
|
|
111
|
+
JSON.stringify({
|
|
112
|
+
error: "Request body schema invalid",
|
|
113
|
+
...body.error,
|
|
114
|
+
}),
|
|
115
|
+
{
|
|
116
|
+
status: 400,
|
|
117
|
+
headers: {
|
|
118
|
+
"Access-Control-Allow-Origin": "*",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
);
|
|
100
122
|
}
|
|
101
123
|
} else {
|
|
102
124
|
response = await route.handler({
|
|
@@ -109,17 +131,21 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
109
131
|
sentry,
|
|
110
132
|
});
|
|
111
133
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
134
|
+
if (response.status === undefined || response.status == 200) {
|
|
135
|
+
const output = route.output.safeParse(response.body);
|
|
136
|
+
if (!output.success) {
|
|
137
|
+
console.error({
|
|
138
|
+
message: "Response body schema invalid",
|
|
139
|
+
error: output.error,
|
|
140
|
+
body: response.body,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
119
143
|
}
|
|
120
144
|
response.headers = response.headers ?? {};
|
|
121
145
|
response.headers["Content-Type"] =
|
|
122
146
|
response.headers["Content-Type"] ?? "application/json";
|
|
147
|
+
response.headers["Access-Control-Allow-Origin"] =
|
|
148
|
+
response.headers["Access-Control-Allow-Origin"] ?? "*";
|
|
123
149
|
return new Response(
|
|
124
150
|
response.headers["Content-Type"] == "application/json"
|
|
125
151
|
? JSON.stringify(response.body)
|
|
@@ -128,13 +154,17 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
128
154
|
);
|
|
129
155
|
}
|
|
130
156
|
}
|
|
131
|
-
return new Response("", {
|
|
157
|
+
return new Response("", {
|
|
158
|
+
status: 404,
|
|
159
|
+
headers: { "Access-Control-Allow-Origin": "*" },
|
|
160
|
+
});
|
|
132
161
|
} catch (err) {
|
|
133
162
|
console.warn(err);
|
|
134
163
|
sentry.captureException(err);
|
|
135
164
|
return new Response("", {
|
|
136
165
|
status: 500,
|
|
137
166
|
statusText: "Internal Server Error",
|
|
167
|
+
headers: { "Access-Control-Allow-Origin": "*" },
|
|
138
168
|
});
|
|
139
169
|
}
|
|
140
170
|
},
|
|
@@ -258,7 +288,7 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
258
288
|
};
|
|
259
289
|
},
|
|
260
290
|
put<I extends ZodSchema, O extends ZodSchema, A, P extends string>(
|
|
261
|
-
auth: Route<T>["auth"],
|
|
291
|
+
auth: Route<T, A>["auth"],
|
|
262
292
|
path: P,
|
|
263
293
|
input: I,
|
|
264
294
|
output: O,
|
|
@@ -285,7 +315,7 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
285
315
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
286
316
|
PP extends ZodObject<any> | undefined,
|
|
287
317
|
>(
|
|
288
|
-
auth: Route<T>["auth"],
|
|
318
|
+
auth: Route<T, A>["auth"],
|
|
289
319
|
path: P,
|
|
290
320
|
input: I,
|
|
291
321
|
output: O,
|
|
@@ -313,7 +343,7 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
313
343
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
314
344
|
PP extends ZodObject<any> | undefined,
|
|
315
345
|
>(
|
|
316
|
-
auth: Route<T>["auth"],
|
|
346
|
+
auth: Route<T, A>["auth"],
|
|
317
347
|
path: P,
|
|
318
348
|
output: O,
|
|
319
349
|
handler: Handler<T, undefined, O, A, P>,
|
|
@@ -332,7 +362,7 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
332
362
|
});
|
|
333
363
|
},
|
|
334
364
|
delete<O extends ZodSchema, A, P extends string>(
|
|
335
|
-
auth: Route<T>["auth"],
|
|
365
|
+
auth: Route<T, A>["auth"],
|
|
336
366
|
path: P,
|
|
337
367
|
output: O,
|
|
338
368
|
handler: Handler<T, undefined, O, A, P>,
|
package/src/openAPI.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { Env, Route } from "./types";
|
|
|
7
7
|
import { ZodIssue, ZodType, z } from "zod";
|
|
8
8
|
|
|
9
9
|
const ZodErrorSchema: ZodType<{ issues: ZodIssue[] }> = z.object({
|
|
10
|
+
error: z.string(),
|
|
10
11
|
issues: z.array(
|
|
11
12
|
z.object({
|
|
12
13
|
code: z.literal("invalid_literal"),
|
|
@@ -31,11 +32,11 @@ type Method =
|
|
|
31
32
|
extendZodWithOpenApi(z);
|
|
32
33
|
|
|
33
34
|
export const openAPI =
|
|
34
|
-
<T extends Env>(
|
|
35
|
+
<T extends Env, A>(
|
|
35
36
|
title: string,
|
|
36
37
|
release: string,
|
|
37
|
-
noAuth: () =>
|
|
38
|
-
routes: Route<T>[],
|
|
38
|
+
noAuth: () => A,
|
|
39
|
+
routes: Route<T, A>[],
|
|
39
40
|
) =>
|
|
40
41
|
async () => {
|
|
41
42
|
const openAPIRegistry = new OpenAPIRegistry();
|
|
@@ -88,6 +89,16 @@ export const openAPI =
|
|
|
88
89
|
},
|
|
89
90
|
},
|
|
90
91
|
},
|
|
92
|
+
500: {
|
|
93
|
+
description: "Invalid Request",
|
|
94
|
+
content: {
|
|
95
|
+
"application/json": {
|
|
96
|
+
schema: z.object({
|
|
97
|
+
error: z.string(),
|
|
98
|
+
}),
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
91
102
|
},
|
|
92
103
|
});
|
|
93
104
|
});
|
package/src/test.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Env } from "./types";
|
|
|
5
5
|
import consumers from "stream/consumers";
|
|
6
6
|
import { URLSearchParams } from "url";
|
|
7
7
|
import createClient from "openapi-fetch";
|
|
8
|
+
import { generateKeyPair, SignJWT, JWTPayload, exportJWK } from "jose";
|
|
8
9
|
|
|
9
10
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
10
11
|
export const setupTests = async <Paths extends {}>(bindings: Env) => {
|
|
@@ -41,6 +42,14 @@ export const setupTests = async <Paths extends {}>(bindings: Env) => {
|
|
|
41
42
|
d1Databases: (config.d1_databases ?? []).map(
|
|
42
43
|
({ binding }: { binding: string }) => binding,
|
|
43
44
|
),
|
|
45
|
+
serviceBindings: Object.fromEntries(
|
|
46
|
+
(config.services ?? []).map(
|
|
47
|
+
({ binding, service }: { binding: string; service: string }) => [
|
|
48
|
+
binding,
|
|
49
|
+
service,
|
|
50
|
+
],
|
|
51
|
+
),
|
|
52
|
+
),
|
|
44
53
|
fetchMock,
|
|
45
54
|
});
|
|
46
55
|
|
|
@@ -65,6 +74,50 @@ export const setupTests = async <Paths extends {}>(bindings: Env) => {
|
|
|
65
74
|
await waitUntil(() => expect(log).contains("Queue batch finished"));
|
|
66
75
|
console.log = orig;
|
|
67
76
|
},
|
|
77
|
+
async fakeJWK(issuer: string, audience: string, claims: JWTPayload) {
|
|
78
|
+
const { publicKey, privateKey } = await generateKeyPair("RS256");
|
|
79
|
+
const jwk = await exportJWK(publicKey);
|
|
80
|
+
jwk.kid = "test";
|
|
81
|
+
jwk.alg = "RS256";
|
|
82
|
+
fetchMock
|
|
83
|
+
.get(bindings[issuer] as string)
|
|
84
|
+
.intercept({ method: "GET", path: "/.well-known/openid-configuration" })
|
|
85
|
+
.reply(
|
|
86
|
+
200,
|
|
87
|
+
{
|
|
88
|
+
jwks_uri: `${bindings[issuer]}/.well-known/jwks`,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
headers: {
|
|
92
|
+
"Content-Type": "application/json",
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
.persist();
|
|
97
|
+
fetchMock
|
|
98
|
+
.get(bindings[issuer] as string)
|
|
99
|
+
.intercept({ method: "GET", path: "/.well-known/jwks" })
|
|
100
|
+
.reply(
|
|
101
|
+
200,
|
|
102
|
+
{
|
|
103
|
+
keys: [jwk],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
headers: {
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
.persist();
|
|
112
|
+
return await new SignJWT(claims)
|
|
113
|
+
.setProtectedHeader({ typ: "JWT", alg: "RS256", kid: jwk.kid })
|
|
114
|
+
.setIssuedAt()
|
|
115
|
+
.setNotBefore("5 minutes ago")
|
|
116
|
+
.setIssuer(bindings[issuer] as string)
|
|
117
|
+
.setAudience([bindings[audience] as string])
|
|
118
|
+
.setExpirationTime("1h")
|
|
119
|
+
.sign(privateKey);
|
|
120
|
+
},
|
|
68
121
|
};
|
|
69
122
|
};
|
|
70
123
|
|
|
@@ -83,11 +136,14 @@ export function recordRequest(
|
|
|
83
136
|
cb: (data: any) => void,
|
|
84
137
|
statusCode: number,
|
|
85
138
|
data: string | object | Buffer | undefined,
|
|
139
|
+
responseOptions?: {
|
|
140
|
+
headers: Record<string, string | string[] | undefined>;
|
|
141
|
+
},
|
|
86
142
|
) {
|
|
87
143
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
88
144
|
return ({ body }: any) => {
|
|
89
145
|
consumers.json(body).then(cb);
|
|
90
|
-
return { statusCode, data };
|
|
146
|
+
return { statusCode, data, responseOptions };
|
|
91
147
|
};
|
|
92
148
|
}
|
|
93
149
|
|
|
@@ -96,6 +152,9 @@ export function recordFormRequest(
|
|
|
96
152
|
cb: (data: any) => void,
|
|
97
153
|
statusCode: number,
|
|
98
154
|
data: string | object | Buffer | undefined,
|
|
155
|
+
responseOptions?: {
|
|
156
|
+
headers: Record<string, string | string[] | undefined>;
|
|
157
|
+
},
|
|
99
158
|
) {
|
|
100
159
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
101
160
|
return ({ body }: any) => {
|
|
@@ -105,7 +164,7 @@ export function recordFormRequest(
|
|
|
105
164
|
.then((data: any) =>
|
|
106
165
|
cb(Object.fromEntries(new URLSearchParams(data).entries())),
|
|
107
166
|
);
|
|
108
|
-
return { statusCode, data };
|
|
167
|
+
return { statusCode, data, responseOptions };
|
|
109
168
|
};
|
|
110
169
|
}
|
|
111
170
|
|
|
@@ -114,6 +173,9 @@ export function recordFirehoseRequest(
|
|
|
114
173
|
cb: (data: any) => void,
|
|
115
174
|
statusCode: number,
|
|
116
175
|
data: string | object | Buffer | undefined,
|
|
176
|
+
responseOptions?: {
|
|
177
|
+
headers: Record<string, string | string[] | undefined>;
|
|
178
|
+
},
|
|
117
179
|
) {
|
|
118
180
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
181
|
return ({ body }: any) => {
|
|
@@ -121,6 +183,6 @@ export function recordFirehoseRequest(
|
|
|
121
183
|
.json(body)
|
|
122
184
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
185
|
.then((data: any) => cb(JSON.parse(atob(data["Record"]["Data"]))));
|
|
124
|
-
return { statusCode, data };
|
|
186
|
+
return { statusCode, data, responseOptions };
|
|
125
187
|
};
|
|
126
188
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import { Toucan } from "toucan-js";
|
|
2
2
|
import { ZodObject, ZodSchema, z } from "zod";
|
|
3
3
|
export type Env = {
|
|
4
|
-
[k: string]: string | Queue | KVNamespace | R2Bucket | D1Database;
|
|
4
|
+
[k: string]: string | Queue | KVNamespace | R2Bucket | D1Database | Fetcher;
|
|
5
5
|
};
|
|
6
|
-
export type Route<T extends Env> =
|
|
6
|
+
export type Route<T extends Env, A> =
|
|
7
7
|
| {
|
|
8
|
-
auth: (c: { req: Request; env: T }) =>
|
|
8
|
+
auth: (c: { req: Request; env: T }) => Promise<A>;
|
|
9
9
|
method: "GET" | "DELETE";
|
|
10
10
|
path: string;
|
|
11
11
|
matcher: RegExp;
|
|
12
12
|
output: ZodSchema;
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
-
handler: Handler<T, undefined, any,
|
|
14
|
+
handler: Handler<T, undefined, any, A, string>;
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
16
|
params?: ZodObject<any>;
|
|
17
17
|
}
|
|
18
18
|
| {
|
|
19
|
-
auth: (c: { req: Request; env: T }) =>
|
|
19
|
+
auth: (c: { req: Request; env: T }) => Promise<A>;
|
|
20
20
|
method: "POST" | "PUT";
|
|
21
21
|
path: string;
|
|
22
22
|
matcher: RegExp;
|
|
23
23
|
input: ZodSchema;
|
|
24
24
|
output: ZodSchema;
|
|
25
25
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
-
handler: Handler<T, any, any,
|
|
26
|
+
handler: Handler<T, any, any, A, string>;
|
|
27
27
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
28
|
params?: ZodObject<any>;
|
|
29
29
|
};
|
|
@@ -47,8 +47,15 @@ export type Handler<
|
|
|
47
47
|
searchParams: URLSearchParams;
|
|
48
48
|
claims: A;
|
|
49
49
|
sentry: Toucan;
|
|
50
|
-
}) => Promise<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
}) => Promise<
|
|
51
|
+
| {
|
|
52
|
+
body: z.infer<O>;
|
|
53
|
+
status?: 200;
|
|
54
|
+
headers?: Record<string, string>;
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
body?: { error: string };
|
|
58
|
+
status: number;
|
|
59
|
+
headers?: Record<string, string>;
|
|
60
|
+
}
|
|
61
|
+
>;
|
package/tsconfig.json
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"esModuleInterop": true,
|
|
4
4
|
"isolatedModules": true,
|
|
5
|
-
"lib": [
|
|
6
|
-
"ESNext"
|
|
7
|
-
],
|
|
5
|
+
"lib": ["ESNext"],
|
|
8
6
|
"module": "ESNext",
|
|
9
7
|
"moduleResolution": "bundler",
|
|
10
8
|
"noUncheckedIndexedAccess": true,
|
|
@@ -12,9 +10,6 @@
|
|
|
12
10
|
"skipLibCheck": true,
|
|
13
11
|
"strict": true,
|
|
14
12
|
"target": "ESNext",
|
|
15
|
-
"types": [
|
|
16
|
-
"@cloudflare/workers-types",
|
|
17
|
-
"vitest/globals"
|
|
18
|
-
]
|
|
13
|
+
"types": ["@cloudflare/workers-types", "vitest/globals"]
|
|
19
14
|
}
|
|
20
|
-
}
|
|
15
|
+
}
|