two-stroke 4.4.2 → 5.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/package.json CHANGED
@@ -8,27 +8,24 @@
8
8
  "type-check": "./bin/type-check.mjs"
9
9
  },
10
10
  "dependencies": {
11
- "@anatine/zod-openapi": "^2.2.8",
12
- "@asteasolutions/zod-to-openapi": "^7.3.0",
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.17",
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.17"
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": "^20.10.0"
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": "4.4.2",
52
+ "version": "5.0.1",
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 { SafeParseReturnType, ZodObject, ZodSchema, z } from "zod";
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 ZodSchema>(
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: SafeParseReturnType<z.input<I>, z.infer<I>>[];
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 ZodSchema, O extends ZodSchema, A, P extends string>(
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 ZodSchema | undefined,
356
- O extends ZodSchema,
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 ZodSchema,
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 ZodSchema,
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,138 @@ export const openAPI =
39
9
  routes: Route<T, A>[],
40
10
  ) =>
41
11
  // eslint-disable-next-line @typescript-eslint/require-await
42
- async () => {
43
- const openAPIRegistry = new OpenAPIRegistry();
44
- openAPIRegistry.registerComponent("securitySchemes", "auth", {
45
- type: "http",
46
- scheme: "bearer",
47
- });
48
- routes.map((route): void => {
49
- const params = z.object(
50
- Object.fromEntries(
51
- Array.from(route.path.matchAll(/\/{(?<name>[^}]*)}/g), (match) => [
52
- match.groups!.name,
53
- z.string(),
54
- ]),
55
- ),
56
- );
57
- openAPIRegistry.registerPath({
58
- method: route.method.toLowerCase() as Method,
59
- path: route.path.toString(),
60
- ...(route.auth === noAuth ? {} : { security: [{ auth: [] }] }),
61
- request:
62
- route.method === "POST" || route.method === "PUT"
63
- ? {
64
- body: route.input
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
+ required: !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
- "application/json": {
68
- schema: route.input,
69
- },
59
+ "application/json": r.input
60
+ ? {
61
+ schema: z.toJSONSchema(r.input, { io: "input" }),
62
+ }
63
+ : undefined,
70
64
  },
71
- required: true,
72
- }
73
- : undefined,
74
- query: route.params,
75
- params,
76
- }
77
- : { query: route.params, params },
78
- responses: {
79
- 200: {
80
- description: "OK",
81
- content: {
82
- "application/json": {
83
- schema: route.output,
65
+ },
66
+ }
67
+ : {}),
68
+ responses: {
69
+ "200": {
70
+ description: "OK",
71
+ content: {
72
+ "application/json": {
73
+ schema: z.toJSONSchema(r.output),
74
+ },
75
+ },
76
+ },
77
+ "400": status400,
78
+ "500": status500,
84
79
  },
85
80
  },
86
81
  },
87
- 400: {
88
- description: "Invalid Request",
89
- content: {
90
- "application/json": {
91
- schema: ZodErrorSchema,
92
- },
93
- },
82
+ ]),
83
+ ),
84
+ },
85
+ });
86
+
87
+ const status500 = {
88
+ description: "Invalid Request",
89
+ content: {
90
+ "application/json": {
91
+ schema: {
92
+ type: "object",
93
+ properties: {
94
+ error: {
95
+ type: "string",
94
96
  },
95
- 500: {
96
- description: "Invalid Request",
97
- content: {
98
- "application/json": {
99
- schema: z.object({
100
- error: z.string(),
101
- }),
97
+ },
98
+ required: ["error"],
99
+ },
100
+ },
101
+ },
102
+ };
103
+ const status400 = {
104
+ description: "Invalid Request",
105
+ content: {
106
+ "application/json": {
107
+ schema: {
108
+ type: "object",
109
+ properties: {
110
+ error: {
111
+ type: "string",
112
+ },
113
+ issues: {
114
+ type: "array",
115
+ items: {
116
+ type: "object",
117
+ properties: {
118
+ code: {
119
+ type: "string",
120
+ enum: ["invalid_literal"],
121
+ },
122
+ expected: {
123
+ type: "string",
124
+ },
125
+ received: {
126
+ type: "string",
127
+ },
128
+ path: {
129
+ type: "array",
130
+ items: {
131
+ type: "string",
132
+ },
133
+ },
134
+ message: {
135
+ type: "string",
136
+ },
102
137
  },
138
+ required: ["code", "expected", "received", "path", "message"],
103
139
  },
104
140
  },
105
141
  },
106
- });
107
- });
108
- const generator = new OpenApiGeneratorV31(openAPIRegistry.definitions);
109
- return {
110
- body: generator.generateDocument({
111
- openapi: "3.1.0",
112
- info: {
113
- title,
114
- version: release,
115
- },
116
- }),
117
- };
118
- };
142
+ required: ["error", "issues"],
143
+ },
144
+ },
145
+ },
146
+ };
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Toucan } from "toucan-js";
2
- import { ZodObject, ZodSchema, z } from "zod";
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: ZodSchema;
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: ZodSchema | undefined;
33
- output: ZodSchema;
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 ZodSchema | undefined,
48
- O extends ZodSchema,
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 ZodSchema ? z.infer<I> : undefined;
54
+ body: I extends ZodType ? z.infer<I> : undefined;
55
55
  params: ExtractParameterNames<P>;
56
56
  searchParams: URLSearchParams;
57
57
  claims: A;