Skip to content

Commit 952d5e5

Browse files
Draft 2
1 parent c7c733b commit 952d5e5

File tree

1 file changed

+41
-82
lines changed

1 file changed

+41
-82
lines changed

src/content/docs/workflows/examples/backup-d1.mdx

Lines changed: 41 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ import {
3030
} from "cloudflare:workers";
3131

3232

33-
// We are using Email Routing to send emails out and D1 for our cart database
33+
// We are using R2 to store the D1 backup
3434
type Env = {
3535
BACKUP_WORKFLOW: Workflow;
3636
D1_REST_API_TOKEN: string;
3737
BACKUP_BUCKET: R2Bucket;
3838
};
3939

40-
// Workflow parameters: we expect a cartId
40+
// Workflow parameters: we expect accountId and databaseId
4141
type Params = {
4242
accountId: string;
4343
databaseId: string;
@@ -46,85 +46,40 @@ type Params = {
4646
// Workflow logic
4747
export class backupWorkflow extends WorkflowEntrypoint<Env, Params> {
4848
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
49-
50-
51-
// Retrieve the cart from the D1 database
52-
// if the cart hasn't been checked out yet retry every 2 minutes, 10 times, otherwise give up
53-
const cart = await step.do(
54-
"retrieve cart",
55-
{
56-
retries: {
57-
limit: 10,
58-
delay: 2000 * 60,
59-
backoff: "constant",
60-
},
61-
timeout: "30 seconds",
62-
},
63-
async () => {
64-
const { results } = await this.env.DB.prepare(
65-
`SELECT * FROM cart WHERE id = ?`,
66-
)
67-
.bind(event.payload.cartId)
68-
.all();
69-
// should return { checkedOut: true, amount: 250 , account: { email: "[email protected]" }};
70-
if(results[0].checkedOut === false) {
71-
throw new Error("cart hasn't been checked out yet");
72-
}
73-
return results[0];
74-
},
75-
);
76-
77-
// Proceed to payment, retry 10 times every minute or give up
78-
const payment = await step.do(
79-
"payment",
80-
{
81-
retries: {
82-
limit: 10,
83-
delay: 1000 * 60,
84-
backoff: "constant",
85-
},
86-
timeout: "30 seconds",
87-
},
88-
async () => {
89-
let resp = await fetch("https://payment-processor.example.com/", {
90-
method: "POST",
91-
headers: {
92-
"Content-Type": "application/json; charset=utf-8",
93-
},
94-
body: JSON.stringify({ amount: cart.amount }),
95-
});
96-
97-
if (!resp.ok) {
98-
throw new Error("payment has failed");
99-
}
100-
101-
return { success: true, amount: cart.amount };
102-
},
103-
);
104-
105-
// Send invoice to the customer, retry 10 times every 5 minutes or give up
106-
// Requires that cart.account.email has previously been validated in Email Routing,
107-
// See https://developers.cloudflare.com/email-routing/email-workers/
108-
await step.do(
109-
"send invoice",
110-
{
111-
retries: {
112-
limit: 10,
113-
delay: 5000 * 60,
114-
backoff: "constant",
115-
},
116-
timeout: "30 seconds",
117-
},
118-
async () => {
119-
const message = genEmail(cart.account.email, payment.amount);
120-
try {
121-
await this.env.SEND_EMAIL.send(message);
122-
} catch (e) {
123-
throw new Error("failed to send invoice");
124-
}
125-
},
126-
);
49+
const { accountId, databaseId } = event.payload;
12750

51+
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/d1/database/${databaseId}/export`;
52+
const method = "POST";
53+
const headers = new Headers();
54+
headers.append("Content-Type", "application/json");
55+
headers.append("Authorization", `Bearer ${this.env.D1_REST_API_TOKEN}`);
56+
57+
const bookmark = step.do(`Starting backup for ${databaseId}`, async () => {
58+
const payload = { output_format: "polling" };
59+
60+
const res = await fetch(url, { method, headers, body: JSON.stringify(payload) });
61+
const { result } = (await res.json()) as any;
62+
63+
// If we don't get `at_bookmark` we throw to retry the step
64+
if (!result?.at_bookmark) throw new Error("Missing `at_bookmark`");
65+
66+
return result.at_bookmark;
67+
});
68+
69+
step.do("Check backup status and store it on R2", async () => {
70+
const payload = { current_bookmark: bookmark };
71+
72+
const res = await fetch(url, { method, headers, body: JSON.stringify(payload) });
73+
const { result } = (await res.json()) as any;
74+
75+
// The endpoint sends `signed_url` when the backup is ready.
76+
// If we don't get `signed_url` we throw to retry the step.
77+
if (!result?.signed_url) throw new Error("Missing `signed_url`");
78+
79+
const dumpResponse = await fetch(result.signed_url);
80+
// We stream the file directly to R2
81+
await this.env.BACKUP_BUCKET.put(result.filename, dumpResponse.body);
82+
});
12883
}
12984
}
13085

@@ -134,8 +89,8 @@ export default {
13489
},
13590
async scheduled(controller: ScheduledController, env: Env, ctx: ExecutionContext) {
13691
const params: Params = {
137-
accountId: "{account_id}",
138-
databaseId: "{database_id},
92+
accountId: "{accountId}",
93+
databaseId: "{databaseId},
13994
};
14095
const instance = await env.BACKUP_WORKFLOW.create({ params });
14196
console.log(`Started workflow: ${instance.id}`);
@@ -171,6 +126,10 @@ name = "backup-workflow"
171126
binding = "BACKUP_WORKFLOW"
172127
class_name = "backupWorkflow"
173128

129+
[[r2_buckets]]
130+
binding = "BACKUP_BUCKET"
131+
bucket_name = "d1-backups"
132+
174133
[triggers]
175134
crons = [ "0 0 * * *" ]
176135

0 commit comments

Comments
 (0)