two-stroke 4.4.1 → 5.0.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/package.json +6 -9
- package/src/index.ts +8 -9
- package/src/open-api.ts +122 -96
- package/src/types.ts +7 -7
package/package.json
CHANGED
|
@@ -8,27 +8,24 @@
|
|
|
8
8
|
"type-check": "./bin/type-check.mjs"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@
|
|
12
|
-
"@
|
|
13
|
-
"@cloudflare/vitest-pool-workers": "^0.8.31",
|
|
14
|
-
"@cloudflare/workers-types": "^4.20250521.0",
|
|
11
|
+
"@cloudflare/vitest-pool-workers": "^0.8.32",
|
|
12
|
+
"@cloudflare/workers-types": "^4.20250522.0",
|
|
15
13
|
"@sentry/cli": "^2.45.0",
|
|
16
14
|
"@types/node": "^22.15.21",
|
|
17
15
|
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
|
18
16
|
"@typescript-eslint/parser": "^8.32.1",
|
|
19
17
|
"@vitest/coverage-istanbul": "^3.1.4",
|
|
20
18
|
"eslint-config-prettier": "^10.1.5",
|
|
21
|
-
"eslint-config-two-stroke": "^1.1.
|
|
19
|
+
"eslint-config-two-stroke": "^1.1.19",
|
|
22
20
|
"eslint-config-typescript": "^3.0.0",
|
|
23
21
|
"jose": "^6.0.11",
|
|
24
22
|
"jwk-subtle": "^1.0.7",
|
|
25
23
|
"miniflare": "^4.20250508.3",
|
|
26
24
|
"openapi-fetch": "^0.14.0",
|
|
27
25
|
"openapi-typescript": "^7.8.0",
|
|
28
|
-
"openapi3-ts": "^4.4.0",
|
|
29
26
|
"pbkdf-subtle": "^1.0.0",
|
|
30
27
|
"toucan-js": "^4.1.1",
|
|
31
|
-
"zod": "^3.25.
|
|
28
|
+
"zod": "^3.25.20"
|
|
32
29
|
},
|
|
33
30
|
"peerDependencies": {
|
|
34
31
|
"@sentry/cli": ">=2",
|
|
@@ -40,7 +37,7 @@
|
|
|
40
37
|
},
|
|
41
38
|
"description": "Simple Cloudflare Worker framework.",
|
|
42
39
|
"engines": {
|
|
43
|
-
"node": "^
|
|
40
|
+
"node": "^22.16.0"
|
|
44
41
|
},
|
|
45
42
|
"eslintConfig": {
|
|
46
43
|
"extends": [
|
|
@@ -52,7 +49,7 @@
|
|
|
52
49
|
"name": "two-stroke",
|
|
53
50
|
"packageManager": "yarn@4.6.0",
|
|
54
51
|
"type": "module",
|
|
55
|
-
"version": "
|
|
52
|
+
"version": "5.0.0",
|
|
56
53
|
"devDependencies": {
|
|
57
54
|
"@types/eslint": "^9.6.1",
|
|
58
55
|
"eslint": "^9.27.0",
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { verify as jwkVerify } from "jwk-subtle";
|
|
2
2
|
import { verify as pbkdfVerify } from "pbkdf-subtle";
|
|
3
3
|
import { Toucan } from "toucan-js";
|
|
4
|
-
import {
|
|
4
|
+
import { ZodSafeParseResult, ZodObject, z, ZodType } from "zod/v4";
|
|
5
5
|
import { openAPI } from "./open-api";
|
|
6
6
|
import { Env, Handler, Route } from "./types";
|
|
7
7
|
|
|
@@ -104,7 +104,6 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
104
104
|
response = await route.handler({
|
|
105
105
|
req,
|
|
106
106
|
env,
|
|
107
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
108
107
|
body: body.data,
|
|
109
108
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
110
109
|
claims,
|
|
@@ -310,13 +309,13 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
310
309
|
}
|
|
311
310
|
throw Error("Invalid");
|
|
312
311
|
},
|
|
313
|
-
queueHandler<I extends
|
|
312
|
+
queueHandler<I extends ZodType>(
|
|
314
313
|
input: I,
|
|
315
314
|
handler: (c: {
|
|
316
315
|
env: T;
|
|
317
316
|
batch: MessageBatch<z.input<I>>;
|
|
318
317
|
sentry: Toucan;
|
|
319
|
-
parsedBatch:
|
|
318
|
+
parsedBatch: ZodSafeParseResult<z.output<I>>[];
|
|
320
319
|
}) => Promise<void>,
|
|
321
320
|
) {
|
|
322
321
|
_queue = async ({ batch, env, sentry }) => {
|
|
@@ -331,7 +330,7 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
331
330
|
console.log("Queue batch finished");
|
|
332
331
|
};
|
|
333
332
|
},
|
|
334
|
-
put<I extends
|
|
333
|
+
put<I extends ZodType, O extends ZodType, A, P extends string>(
|
|
335
334
|
auth: Route<T, A>["auth"],
|
|
336
335
|
path: P,
|
|
337
336
|
input: I,
|
|
@@ -352,8 +351,8 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
352
351
|
},
|
|
353
352
|
|
|
354
353
|
post<
|
|
355
|
-
I extends
|
|
356
|
-
O extends
|
|
354
|
+
I extends ZodType | undefined,
|
|
355
|
+
O extends ZodType,
|
|
357
356
|
A,
|
|
358
357
|
P extends string,
|
|
359
358
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -381,7 +380,7 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
381
380
|
},
|
|
382
381
|
|
|
383
382
|
get<
|
|
384
|
-
O extends
|
|
383
|
+
O extends ZodType,
|
|
385
384
|
A,
|
|
386
385
|
P extends string,
|
|
387
386
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -406,7 +405,7 @@ export function twoStroke<T extends Env>(title: string, release: string) {
|
|
|
406
405
|
});
|
|
407
406
|
},
|
|
408
407
|
delete<
|
|
409
|
-
O extends
|
|
408
|
+
O extends ZodType,
|
|
410
409
|
A,
|
|
411
410
|
P extends string,
|
|
412
411
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
package/src/open-api.ts
CHANGED
|
@@ -1,35 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
OpenAPIRegistry,
|
|
3
|
-
OpenApiGeneratorV31,
|
|
4
|
-
extendZodWithOpenApi,
|
|
5
|
-
} from "@asteasolutions/zod-to-openapi";
|
|
1
|
+
import { z, ZodType } from "zod/v4";
|
|
6
2
|
import { Env, Route } from "./types";
|
|
7
|
-
import { ZodIssue, ZodType, z } from "zod";
|
|
8
|
-
|
|
9
|
-
const ZodErrorSchema: ZodType<{ issues: ZodIssue[] }> = z.object({
|
|
10
|
-
error: z.string(),
|
|
11
|
-
issues: z.array(
|
|
12
|
-
z.object({
|
|
13
|
-
code: z.literal("invalid_literal"),
|
|
14
|
-
expected: z.string(),
|
|
15
|
-
received: z.string(),
|
|
16
|
-
path: z.array(z.string()),
|
|
17
|
-
message: z.string(),
|
|
18
|
-
}),
|
|
19
|
-
),
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
type Method =
|
|
23
|
-
| "get"
|
|
24
|
-
| "post"
|
|
25
|
-
| "put"
|
|
26
|
-
| "delete"
|
|
27
|
-
| "patch"
|
|
28
|
-
| "head"
|
|
29
|
-
| "options"
|
|
30
|
-
| "trace";
|
|
31
|
-
|
|
32
|
-
extendZodWithOpenApi(z);
|
|
33
3
|
|
|
34
4
|
export const openAPI =
|
|
35
5
|
<T extends Env, A>(
|
|
@@ -39,80 +9,136 @@ export const openAPI =
|
|
|
39
9
|
routes: Route<T, A>[],
|
|
40
10
|
) =>
|
|
41
11
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
42
|
-
async () => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
12
|
+
async () => ({
|
|
13
|
+
body: {
|
|
14
|
+
openapi: "3.1.0",
|
|
15
|
+
info: {
|
|
16
|
+
title,
|
|
17
|
+
version: release,
|
|
18
|
+
},
|
|
19
|
+
components: {
|
|
20
|
+
securitySchemes: {
|
|
21
|
+
auth: {
|
|
22
|
+
type: "http",
|
|
23
|
+
scheme: "bearer",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
paths: Object.fromEntries(
|
|
28
|
+
routes.map((r) => [
|
|
29
|
+
r.path,
|
|
30
|
+
{
|
|
31
|
+
[r.method.toLocaleLowerCase()]: {
|
|
32
|
+
parameters: [
|
|
33
|
+
...Object.entries(
|
|
34
|
+
(r.params?.shape ?? {}) as Record<string, ZodType>,
|
|
35
|
+
).map(([k, v]) => ({
|
|
36
|
+
name: k,
|
|
37
|
+
in: "query",
|
|
38
|
+
requireed: !v.safeParse(undefined).success,
|
|
39
|
+
schema: z.toJSONSchema(v, { io: "input" }),
|
|
40
|
+
})),
|
|
41
|
+
...Array.from(
|
|
42
|
+
r.path.matchAll(/\/{(?<name>[^}]*)}/g),
|
|
43
|
+
(match) => ({
|
|
44
|
+
name: match.groups!.name,
|
|
45
|
+
in: "path",
|
|
46
|
+
required: true,
|
|
47
|
+
schema: {
|
|
48
|
+
type: "string",
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
],
|
|
53
|
+
...(r.auth === noAuth ? {} : { security: [{ auth: [] }] }),
|
|
54
|
+
...(r.method === "POST" || r.method === "PUT"
|
|
55
|
+
? {
|
|
56
|
+
requestBody: {
|
|
57
|
+
required: true,
|
|
66
58
|
content: {
|
|
67
59
|
"application/json": {
|
|
68
|
-
schema:
|
|
60
|
+
schema: z.toJSONSchema(r.input!, { io: "input" }),
|
|
69
61
|
},
|
|
70
62
|
},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
: {}),
|
|
66
|
+
responses: {
|
|
67
|
+
"200": {
|
|
68
|
+
description: "OK",
|
|
69
|
+
content: {
|
|
70
|
+
"application/json": {
|
|
71
|
+
schema: z.toJSONSchema(r.output),
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
"400": status400,
|
|
76
|
+
"500": status500,
|
|
84
77
|
},
|
|
85
78
|
},
|
|
86
79
|
},
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
80
|
+
]),
|
|
81
|
+
),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const status500 = {
|
|
86
|
+
description: "Invalid Request",
|
|
87
|
+
content: {
|
|
88
|
+
"application/json": {
|
|
89
|
+
schema: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
error: {
|
|
93
|
+
type: "string",
|
|
94
94
|
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
},
|
|
96
|
+
required: ["error"],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
const status400 = {
|
|
102
|
+
description: "Invalid Request",
|
|
103
|
+
content: {
|
|
104
|
+
"application/json": {
|
|
105
|
+
schema: {
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: {
|
|
108
|
+
error: {
|
|
109
|
+
type: "string",
|
|
110
|
+
},
|
|
111
|
+
issues: {
|
|
112
|
+
type: "array",
|
|
113
|
+
items: {
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: {
|
|
116
|
+
code: {
|
|
117
|
+
type: "string",
|
|
118
|
+
enum: ["invalid_literal"],
|
|
119
|
+
},
|
|
120
|
+
expected: {
|
|
121
|
+
type: "string",
|
|
122
|
+
},
|
|
123
|
+
received: {
|
|
124
|
+
type: "string",
|
|
125
|
+
},
|
|
126
|
+
path: {
|
|
127
|
+
type: "array",
|
|
128
|
+
items: {
|
|
129
|
+
type: "string",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
message: {
|
|
133
|
+
type: "string",
|
|
134
|
+
},
|
|
102
135
|
},
|
|
136
|
+
required: ["code", "expected", "received", "path", "message"],
|
|
103
137
|
},
|
|
104
138
|
},
|
|
105
139
|
},
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
openapi: "3.1.0",
|
|
112
|
-
info: {
|
|
113
|
-
title,
|
|
114
|
-
version: release,
|
|
115
|
-
},
|
|
116
|
-
}),
|
|
117
|
-
};
|
|
118
|
-
};
|
|
140
|
+
required: ["error", "issues"],
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Toucan } from "toucan-js";
|
|
2
|
-
import {
|
|
2
|
+
import { ZodType, ZodObject, z } from "zod/v4";
|
|
3
3
|
export type Env = {
|
|
4
4
|
[k: string]:
|
|
5
5
|
| string
|
|
@@ -18,7 +18,7 @@ export type Route<T extends Env, A> =
|
|
|
18
18
|
method: "GET" | "DELETE";
|
|
19
19
|
path: string;
|
|
20
20
|
matcher: RegExp;
|
|
21
|
-
output:
|
|
21
|
+
output: ZodType;
|
|
22
22
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
23
|
handler: Handler<T, undefined, any, A, string>;
|
|
24
24
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -29,8 +29,8 @@ export type Route<T extends Env, A> =
|
|
|
29
29
|
method: "POST" | "PUT";
|
|
30
30
|
path: string;
|
|
31
31
|
matcher: RegExp;
|
|
32
|
-
input:
|
|
33
|
-
output:
|
|
32
|
+
input: ZodType | undefined;
|
|
33
|
+
output: ZodType;
|
|
34
34
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
35
|
handler: Handler<T, any, any, A, string>;
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -44,14 +44,14 @@ export type ExtractParameterNames<S extends string> =
|
|
|
44
44
|
|
|
45
45
|
export type Handler<
|
|
46
46
|
T extends Env,
|
|
47
|
-
I extends
|
|
48
|
-
O extends
|
|
47
|
+
I extends ZodType | undefined,
|
|
48
|
+
O extends ZodType,
|
|
49
49
|
A,
|
|
50
50
|
P extends string,
|
|
51
51
|
> = (c: {
|
|
52
52
|
req: Request;
|
|
53
53
|
env: T;
|
|
54
|
-
body: I extends
|
|
54
|
+
body: I extends ZodType ? z.infer<I> : undefined;
|
|
55
55
|
params: ExtractParameterNames<P>;
|
|
56
56
|
searchParams: URLSearchParams;
|
|
57
57
|
claims: A;
|