Skip to content

Commit e967de8

Browse files
committed
add example to handle sql migrations in DO
1 parent 561e7a7 commit e967de8

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
---
2+
type: example
3+
summary: Mange SQL migrations in a Durable Object.
4+
tags:
5+
- Durable Objects
6+
pcx_content_type: example
7+
title: Handle SQL migrations
8+
sidebar:
9+
order: 3
10+
description: Mange SQL migrations in a Durable Object.
11+
---
12+
13+
import { TypeScriptExample, WranglerConfig } from "~/components";
14+
15+
This example shows how to handle SQL migrations in a Durable Object. To use this example, make sure that the `id` of the migrations are sequential.
16+
17+
<TypeScriptExample>
18+
19+
```ts
20+
import { DurableObject } from "cloudflare:workers";
21+
22+
type SQLMigration = {
23+
id: number; // should be sequential
24+
description: string; // description of the migration
25+
sql: string; // SQL statement to run
26+
};
27+
28+
// add your migrations here
29+
const migrations: SQLMigration[] = [
30+
{
31+
id: 1,
32+
description: "Create 'users' table",
33+
sql: `
34+
CREATE TABLE users (
35+
id INTEGER PRIMARY KEY,
36+
name TEXT NOT NULL
37+
);
38+
`,
39+
},
40+
{
41+
id: 2,
42+
description: "Add age column",
43+
sql: `
44+
ALTER TABLE users ADD COLUMN age INTEGER;`,
45+
},
46+
];
47+
48+
// Handles the SQL migrations
49+
async function runMigrations(
50+
storage: DurableObjectStorage,
51+
): Promise<{ rowsRead: number; rowsWritten: number }> {
52+
const result = {
53+
rowsRead: 0,
54+
rowsWritten: 0,
55+
};
56+
57+
// fetch the last migration version that was run
58+
const currentVersion = (await storage.get<number>("migration")) ?? 0;
59+
60+
// filter out the migrations that have not been run
61+
const pendingMigrations = migrations.filter((m) => m.id > currentVersion);
62+
63+
// no migrations to run
64+
if (pendingMigrations.length === 0) {
65+
return result;
66+
}
67+
68+
try {
69+
await storage.transaction(async () => {
70+
for (let migration of pendingMigrations) {
71+
console.log(
72+
`Running migration ${migration.id}: ${migration.description}`,
73+
);
74+
const cursor = storage.sql.exec(migration.sql);
75+
let _ = cursor.toArray();
76+
result.rowsRead += cursor.rowsRead;
77+
result.rowsWritten += cursor.rowsWritten;
78+
// store the migration version that was run
79+
await storage.put("migration", migration.id);
80+
}
81+
});
82+
return result;
83+
} catch (e) {
84+
console.error(e);
85+
throw new Error("Migration failed");
86+
}
87+
}
88+
89+
export class MigrationExampleDO extends DurableObject<Env> {
90+
storage: DurableObjectStorage;
91+
92+
constructor(ctx: DurableObjectState, env: Env) {
93+
super(ctx, env);
94+
this.storage = ctx.storage;
95+
}
96+
97+
// inserts a user in the user table
98+
async insertUser(name: string) {
99+
// run migrations before write
100+
await runMigrations(this.storage);
101+
102+
return this.storage.sql.exec(
103+
`INSERT INTO users (name) VALUES ('${name}');`,
104+
);
105+
}
106+
}
107+
108+
export default {
109+
/**
110+
* This is the standard fetch handler for a Cloudflare Worker
111+
*
112+
* @param request - The request submitted to the Worker from the client
113+
* @param env - The interface to reference bindings declared in wrangler.jsonc
114+
* @param ctx - The execution context of the Worker
115+
* @returns The response to be sent back to the client
116+
*/
117+
async fetch(request, env, ctx): Promise<Response> {
118+
// We will create a `DurableObjectId` using the pathname from the Worker request
119+
// This id refers to a unique instance of our 'MigrationExampleDO' class above
120+
let id: DurableObjectId = env.MIGRATION_EXAMPLE_DO.idFromName(
121+
new URL(request.url).pathname,
122+
);
123+
124+
// This stub creates a communication channel with the Durable Object instance
125+
// The Durable Object constructor will be invoked upon the first call for a given id
126+
let stub = env.MIGRATION_EXAMPLE_DO.get(id);
127+
128+
// Inserts a user into the 'users' table
129+
stub.insertUser("John");
130+
131+
return new Response("User inserted", { status: 200 });
132+
},
133+
} satisfies ExportedHandler<Env>;
134+
```
135+
136+
</TypeScriptExample>
137+
138+
Finally, configure your Wrangler file to include a Durable Object [binding](/durable-objects/get-started/tutorial/#5-configure-durable-object-bindings) and [migration](/durable-objects/reference/durable-objects-migrations/) based on the namespace and class name chosen previously.
139+
140+
<WranglerConfig>
141+
142+
```toml title="wrangler.toml"
143+
name = "sql-migration-do"
144+
145+
[[durable_objects.bindings]]
146+
name = "MIGRATION_EXAMPLE_DO"
147+
class_name = "MigrationExampleDO"
148+
149+
[[migrations]]
150+
tag = "v1"
151+
new_sqlite_classes = ["MigrationExampleDO"]
152+
```
153+
154+
</WranglerConfig>
155+
156+
### Related resources
157+
158+
- [SQL Storage](/durable-objects/api/sql-storage)
159+
- [Workers RPC](/workers/runtime-apis/rpc/)
160+
- [Zero-latency SQLite storage in every Durable Object](https://blog.cloudflare.com/sqlite-in-durable-objects/).

0 commit comments

Comments
 (0)