better-cf 1.0.0 → 2.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/README.md +153 -77
- package/dist/cli/index.js +2864 -999
- package/dist/cli/index.js.map +1 -1
- package/dist/durable-object/index.d.ts +7 -0
- package/dist/durable-object/index.js +244 -0
- package/dist/durable-object/index.js.map +1 -0
- package/dist/durable-object/internal.d.ts +96 -0
- package/dist/durable-object/internal.js +427 -0
- package/dist/durable-object/internal.js.map +1 -0
- package/dist/testing/index.d.ts +16 -1
- package/dist/testing/index.js +342 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/types-C1mNTuC-.d.ts +239 -0
- package/package.json +24 -4
package/README.md
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
# better-cf
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Opininated Functional, type-safe Cloudflare SDKs for Durable Objects and Queues.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
`better-cf` keeps Cloudflare Queue primitives but improves day-to-day ergonomics:
|
|
5
|
+
## Canonical Imports
|
|
8
6
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
7
|
+
- Primary next-gen surface: `better-cf/durable-object`
|
|
8
|
+
- Legacy queue surface: `better-cf/queue`
|
|
9
|
+
- Testing helpers: `better-cf/testing`
|
|
10
|
+
- CLI binary: `better-cf`
|
|
13
11
|
|
|
14
|
-
## Quickstart
|
|
12
|
+
## Quickstart
|
|
15
13
|
|
|
16
14
|
```bash
|
|
17
15
|
npm i better-cf zod
|
|
@@ -20,7 +18,7 @@ npx better-cf init
|
|
|
20
18
|
npm run dev
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
Create a new worker:
|
|
24
22
|
|
|
25
23
|
```bash
|
|
26
24
|
npx better-cf create my-worker
|
|
@@ -28,78 +26,132 @@ cd my-worker
|
|
|
28
26
|
npm run dev
|
|
29
27
|
```
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
## Primary Surface: `better-cf/durable-object`
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
Use `schema.ts` as the resource registry, keep runtime behavior in sibling files, and call everything through generated `ctx.api`.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// better-cf.config.ts
|
|
35
|
+
import { createSDK } from 'better-cf/durable-object';
|
|
36
|
+
|
|
37
|
+
export const sdk = createSDK();
|
|
38
|
+
export const defineWorker = sdk.defineWorker;
|
|
36
39
|
```
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
```ts
|
|
42
|
+
// src/schema.ts
|
|
43
|
+
import { z } from 'zod';
|
|
44
|
+
import { sdk } from '../better-cf.config';
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
export const room = sdk.defineDurableObject({
|
|
47
|
+
name: 'Room',
|
|
48
|
+
key: z.string(),
|
|
49
|
+
version: 1,
|
|
50
|
+
description: 'Chat room state.'
|
|
51
|
+
});
|
|
43
52
|
|
|
44
|
-
|
|
53
|
+
export const emailQueue = sdk.defineQueue({
|
|
54
|
+
description: 'Email fanout queue.',
|
|
55
|
+
args: z.object({
|
|
56
|
+
roomId: z.string(),
|
|
57
|
+
to: z.string().email(),
|
|
58
|
+
body: z.string()
|
|
59
|
+
}),
|
|
60
|
+
retry: 3,
|
|
61
|
+
retryDelay: '30s'
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// src/room.ts
|
|
67
|
+
import { z } from 'zod';
|
|
68
|
+
import { room } from './schema';
|
|
69
|
+
|
|
70
|
+
export const sendMessage = room.fn({
|
|
71
|
+
description: 'Append a room message.',
|
|
72
|
+
args: z.object({
|
|
73
|
+
body: z.string(),
|
|
74
|
+
author: z.string()
|
|
75
|
+
}),
|
|
76
|
+
returns: z.object({
|
|
77
|
+
ok: z.literal(true)
|
|
78
|
+
}),
|
|
79
|
+
handler: async ({ storage }, args) => {
|
|
80
|
+
const messages = ((await storage.get('messages')) as unknown[] | undefined) ?? [];
|
|
81
|
+
messages.push(args);
|
|
82
|
+
await storage.put('messages', messages);
|
|
83
|
+
return { ok: true };
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
```
|
|
45
87
|
|
|
46
|
-
|
|
88
|
+
```ts
|
|
89
|
+
// src/email-queue.ts
|
|
90
|
+
import { emailQueue } from './schema';
|
|
91
|
+
|
|
92
|
+
export const emailQueueConsumer = emailQueue.message({
|
|
93
|
+
description: 'Deliver one email message.',
|
|
94
|
+
handler: async (ctx, job) => {
|
|
95
|
+
await ctx.api.room.sendMessage(job.roomId, {
|
|
96
|
+
body: job.body,
|
|
97
|
+
author: 'system'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
```
|
|
47
102
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
4. patch wrangler queue sections
|
|
52
|
-
5. infer env types
|
|
53
|
-
6. run/restart `wrangler dev`
|
|
54
|
-
7. re-run on source/config changes
|
|
103
|
+
```ts
|
|
104
|
+
// worker.ts
|
|
105
|
+
import { defineWorker } from './better-cf.config';
|
|
55
106
|
|
|
56
|
-
|
|
107
|
+
export default defineWorker({
|
|
108
|
+
async fetch(_request, ctx) {
|
|
109
|
+
await ctx.api.room.sendMessage('general', {
|
|
110
|
+
body: 'hello',
|
|
111
|
+
author: 'abhi'
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await ctx.api.emailQueue.send({
|
|
115
|
+
roomId: 'general',
|
|
116
|
+
to: 'team@example.com',
|
|
117
|
+
body: 'New message'
|
|
118
|
+
});
|
|
57
119
|
|
|
58
|
-
|
|
59
|
-
|
|
120
|
+
return new Response('ok');
|
|
121
|
+
}
|
|
122
|
+
});
|
|
60
123
|
```
|
|
61
124
|
|
|
62
|
-
|
|
125
|
+
What the generator adds:
|
|
126
|
+
|
|
127
|
+
- `.better-cf/entry.ts` with worker, queue, and Durable Object wiring
|
|
128
|
+
- `.better-cf/types.d.ts` with `ctx.api` typing and JSDoc
|
|
129
|
+
- Wrangler queue and `durable_objects` bindings
|
|
130
|
+
- SQLite Durable Object migrations
|
|
63
131
|
|
|
64
|
-
|
|
132
|
+
## Legacy Queue Surface
|
|
133
|
+
|
|
134
|
+
`better-cf/queue` remains available for the original inline-consumer model.
|
|
65
135
|
|
|
66
136
|
```ts
|
|
67
137
|
import { createSDK } from 'better-cf/queue';
|
|
68
138
|
import { z } from 'zod';
|
|
69
139
|
|
|
70
|
-
|
|
71
|
-
const { defineQueue, defineWorker } = createSDK<Env>();
|
|
140
|
+
const { defineQueue } = createSDK();
|
|
72
141
|
|
|
73
142
|
export const signupQueue = defineQueue({
|
|
74
|
-
|
|
75
|
-
|
|
143
|
+
args: z.object({ email: z.string().email() }),
|
|
144
|
+
handler: async (ctx, msg) => {
|
|
76
145
|
console.log(ctx.message.id, msg.email);
|
|
77
|
-
},
|
|
78
|
-
retry: 3,
|
|
79
|
-
retryDelay: '30s'
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
export default defineWorker({
|
|
83
|
-
async fetch() {
|
|
84
|
-
return new Response('ok');
|
|
85
146
|
}
|
|
86
147
|
});
|
|
87
148
|
```
|
|
88
149
|
|
|
89
|
-
|
|
150
|
+
Use this surface for existing projects. New projects should prefer `better-cf/durable-object`.
|
|
90
151
|
|
|
91
|
-
|
|
92
|
-
const auditQueue = defineQueue({
|
|
93
|
-
message: z.object({ action: z.string() }),
|
|
94
|
-
batch: { maxSize: 10, timeout: '30s', maxConcurrency: 2 },
|
|
95
|
-
processBatch: async (ctx, messages) => {
|
|
96
|
-
console.log(messages.length, ctx.batch.queue);
|
|
97
|
-
ctx.batch.ackAll();
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
```
|
|
152
|
+
## Testing
|
|
101
153
|
|
|
102
|
-
|
|
154
|
+
Legacy inline queues:
|
|
103
155
|
|
|
104
156
|
```ts
|
|
105
157
|
import { testQueue } from 'better-cf/testing';
|
|
@@ -112,35 +164,59 @@ const result = await testQueue(signupQueue, {
|
|
|
112
164
|
expect(result.acked).toHaveLength(1);
|
|
113
165
|
```
|
|
114
166
|
|
|
115
|
-
|
|
167
|
+
Next-gen external consumers and durable functions:
|
|
116
168
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
169
|
+
```ts
|
|
170
|
+
import { testDurableFunction, testQueueConsumer } from 'better-cf/testing';
|
|
171
|
+
|
|
172
|
+
await testDurableFunction(sendMessage, {
|
|
173
|
+
env: {},
|
|
174
|
+
args: { body: 'hello', author: 'abhi' }
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await testQueueConsumer(emailQueue, emailQueueConsumer, {
|
|
178
|
+
env: {},
|
|
179
|
+
api: {
|
|
180
|
+
room: {
|
|
181
|
+
sendMessage: async () => ({ ok: true })
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
message: {
|
|
185
|
+
roomId: 'general',
|
|
186
|
+
to: 'team@example.com',
|
|
187
|
+
body: 'hello'
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Core Workflow
|
|
123
193
|
|
|
124
|
-
|
|
194
|
+
`better-cf dev` continuously:
|
|
125
195
|
|
|
126
|
-
|
|
196
|
+
1. scans declarations and external registrations
|
|
197
|
+
2. validates config and ownership rules
|
|
198
|
+
3. generates `.better-cf/entry.ts` and type files
|
|
199
|
+
4. patches Wrangler queues, Durable Objects, and SQLite migrations
|
|
200
|
+
5. infers env types
|
|
201
|
+
6. runs or restarts `wrangler dev`
|
|
127
202
|
|
|
128
|
-
|
|
203
|
+
One-shot generation:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
better-cf generate
|
|
207
|
+
```
|
|
129
208
|
|
|
130
|
-
|
|
131
|
-
- Queue metrics/dashboard abstraction
|
|
132
|
-
- Dynamic runtime queue declaration
|
|
133
|
-
- Unsupported remote queue local-dev parity modes
|
|
209
|
+
## Scope
|
|
134
210
|
|
|
135
|
-
|
|
211
|
+
The next-gen surface intentionally stays thin over Cloudflare primitives:
|
|
136
212
|
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
-
|
|
213
|
+
- typed Durable Object RPC methods, alarms, fetch, init, and WebSocket hibernation hooks
|
|
214
|
+
- typed queue producers and external consumers
|
|
215
|
+
- generated `ctx.api` clients and Wrangler wiring
|
|
216
|
+
- raw escape hatches for native Durable Object namespace methods when needed
|
|
140
217
|
|
|
141
|
-
Use native Cloudflare APIs directly
|
|
218
|
+
Use native Cloudflare APIs directly when the SDK intentionally does not abstract an edge case yet.
|
|
142
219
|
|
|
143
220
|
## Docs
|
|
144
221
|
|
|
145
|
-
|
|
146
|
-
During migration, legacy docs source may still exist under `apps/docs`, but it is no longer part of package CI/CD.
|
|
222
|
+
The full docs live under `apps/docs`, including the new Durable Object section, coverage matrix, migration guide, and legacy queue docs.
|