two-stroke 4.2.0 → 4.3.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/README.md +146 -1
- package/bin/test.mjs +3 -5
- package/package.json +1 -1
- package/src/open-api.ts +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,148 @@
|
|
|
1
1
|
# two-stroke
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A minimalist framework for Cloudflare Workers with built-in routing, authentication, and validation.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Two-Stroke is a lightweight framework for building APIs with Cloudflare Workers. It provides a structured approach to defining routes, handling authentication, validating requests and responses with Zod, and managing errors with Sentry.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Type-safe routing** with path parameter extraction
|
|
12
|
+
- **Schema validation** for request and response bodies using Zod
|
|
13
|
+
- **Built-in authentication** methods (JWT, PBKDF)
|
|
14
|
+
- **Error handling** with Sentry integration
|
|
15
|
+
- **CORS support** out of the box
|
|
16
|
+
- **Queue handling** for background processing
|
|
17
|
+
- **Cron job support** for scheduled tasks
|
|
18
|
+
- **Email handling** capabilities
|
|
19
|
+
- **OpenAPI documentation** generation
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install two-stroke
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { twoStroke } from "two-stroke";
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
|
|
33
|
+
// Define your environment type
|
|
34
|
+
type MyEnv = {
|
|
35
|
+
MY_SECRET: string;
|
|
36
|
+
MY_KV: KVNamespace;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Create a Two-Stroke app
|
|
40
|
+
const app = twoStroke<MyEnv>("My API", "1.0.0");
|
|
41
|
+
|
|
42
|
+
// Define routes
|
|
43
|
+
app.get(
|
|
44
|
+
app.noAuth,
|
|
45
|
+
"/hello",
|
|
46
|
+
z.object({ message: z.string() }),
|
|
47
|
+
async ({ env }) => {
|
|
48
|
+
return {
|
|
49
|
+
body: { message: "Hello, World!" },
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Define a route with path parameters
|
|
55
|
+
app.get(
|
|
56
|
+
app.noAuth,
|
|
57
|
+
"/users/{userId}",
|
|
58
|
+
z.object({ user: z.object({ id: z.string(), name: z.string() }) }),
|
|
59
|
+
async ({ params }) => {
|
|
60
|
+
return {
|
|
61
|
+
body: { user: { id: params.userId, name: "John Doe" } },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Define a POST route with request validation
|
|
67
|
+
app.post(
|
|
68
|
+
app.noAuth,
|
|
69
|
+
"/messages",
|
|
70
|
+
z.object({ content: z.string().min(1) }),
|
|
71
|
+
z.object({ id: z.string() }),
|
|
72
|
+
async ({ body }) => {
|
|
73
|
+
return {
|
|
74
|
+
body: { id: "msg_123" },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Export the worker handlers
|
|
80
|
+
export default app;
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Authentication
|
|
84
|
+
|
|
85
|
+
Two-Stroke provides several authentication methods out of the box:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// No authentication
|
|
89
|
+
app.get(app.noAuth, "/public", z.object({ message: z.string() }), async () => ({
|
|
90
|
+
body: { message: "Public endpoint" },
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
// PBKDF authentication
|
|
94
|
+
app.get(
|
|
95
|
+
app.pbkdf("API_KEY"),
|
|
96
|
+
"/protected",
|
|
97
|
+
z.object({ message: z.string() }),
|
|
98
|
+
async () => ({ body: { message: "Protected endpoint" } })
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// JWT authentication
|
|
102
|
+
app.get(
|
|
103
|
+
app.jwt<{ userId: string }>("JWT_SECRET", "JWT_AUDIENCE"),
|
|
104
|
+
"/user-data",
|
|
105
|
+
z.object({ userId: z.string() }),
|
|
106
|
+
async ({ claims }) => ({ body: { userId: claims.userId } })
|
|
107
|
+
);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Queue Handling
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Define a queue handler
|
|
114
|
+
app.queueHandler(
|
|
115
|
+
z.object({ id: z.string() }),
|
|
116
|
+
async ({ batch, parsedBatch, env }) => {
|
|
117
|
+
for (let i = 0; i < batch.messages.length; i++) {
|
|
118
|
+
if (parsedBatch[i].success) {
|
|
119
|
+
const data = parsedBatch[i].data;
|
|
120
|
+
// Process queue message
|
|
121
|
+
console.log(`Processing message: ${data.id}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Add to queue with retry logic
|
|
128
|
+
import { addToQueue } from "two-stroke";
|
|
129
|
+
|
|
130
|
+
await addToQueue(
|
|
131
|
+
env.MY_QUEUE,
|
|
132
|
+
{ id: "task_123" },
|
|
133
|
+
{
|
|
134
|
+
retries: 3,
|
|
135
|
+
backoffFactor: 2,
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Scheduled Tasks
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// Define a scheduled task
|
|
144
|
+
app.schedule("*/15 * * * *", async ({ env }) => {
|
|
145
|
+
// Run every 15 minutes
|
|
146
|
+
console.log("Running scheduled task");
|
|
147
|
+
});
|
|
148
|
+
```
|
package/bin/test.mjs
CHANGED
|
@@ -31,11 +31,9 @@ if (fs.existsSync("wrangler.toml")) {
|
|
|
31
31
|
"",
|
|
32
32
|
ts.ScriptTarget.Latest,
|
|
33
33
|
);
|
|
34
|
-
const result =
|
|
35
|
-
ts.EmitHint.Unspecified,
|
|
36
|
-
|
|
37
|
-
resultFile,
|
|
38
|
-
);
|
|
34
|
+
const result = types
|
|
35
|
+
.map((t) => printer.printNode(ts.EmitHint.Unspecified, t, resultFile))
|
|
36
|
+
.join("\n\n");
|
|
39
37
|
fs.writeFileSync(
|
|
40
38
|
"test/api.d.ts",
|
|
41
39
|
await prettier.format(result, { parser: "typescript" }),
|
package/package.json
CHANGED
package/src/open-api.ts
CHANGED